npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

adapty

v0.9.0

Published

Adapty command line interface

Downloads

2,305

Readme

Adapty CLI

Adapty Developer CLI. Manage apps, products, paywalls, placements, flows, and access levels from your terminal.

Installation

npm install -g adapty

Requires Node.js 22 or 24. Node 18 and 20 are past end-of-life and are not supported.

Authentication

adapty auth login

Opens browser for OAuth device flow. Token is stored in ~/.config/adapty/config.json.

Override with ADAPTY_TOKEN environment variable:

ADAPTY_TOKEN=your-token adapty apps list

Other auth commands:

adapty auth whoami     # verify token, show user info
adapty auth status     # show local auth state
adapty auth logout     # clear stored credentials and migration selection (local only)
adapty auth revoke     # revoke active token and clear matching credentials and selection

auth revoke uses ADAPTY_TOKEN when set, otherwise the stored token. A different token in the session file is preserved. After revoking an environment token, unset ADAPTY_TOKEN; a preserved stored session will then become active again. With no token, revoke succeeds without a request and returns {"status":"not_authenticated"} under --json.

auth logout removes the saved migration selection even without stored credentials or with a malformed context. auth revoke removes selection only after server success and only when it belongs to the revoked token. Failed revocation preserves both files. Both cleanup operations are attempted independently; incomplete cleanup returns exit 1 (auth_cleanup_failed). If the server already revoked the token, the error says so: fix local files without repeating revocation. Environment variables remain in the parent shell; unset ADAPTY_TOKEN and ADAPTY_MIGRATION there when needed.

Commands

All resource commands require --app APP_ID (UUID). Use adapty apps list to find your app ID.

Apps

adapty apps list [--page N] [--page-size N]
adapty apps get APP_ID
adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app
adapty apps update APP_ID [flags]

Products

adapty products list --app UUID [--page N] [--page-size N]
adapty products get --app UUID PRODUCT_ID
adapty products create --app UUID [flags]
adapty products update --app UUID PRODUCT_ID [flags]

Paywalls

adapty paywalls list --app UUID [--page N] [--page-size N]
adapty paywalls get --app UUID PAYWALL_ID
adapty paywalls create --app UUID --title "Name" --product-id UUID1 [--product-id UUID2]
adapty paywalls update --app UUID PAYWALL_ID [flags]

Placements

adapty placements list --app UUID [--page N] [--page-size N]
adapty placements get --app UUID PLACEMENT_ID
adapty placements create --app UUID [flags]
adapty placements update --app UUID PLACEMENT_ID [flags]

Flows

adapty flows list --app UUID [--page N] [--page-size N]
adapty flows get --app UUID FLOW_ID
adapty flows create --app UUID --name "Name"
adapty flows config get --app UUID FLOW_ID
adapty flows config update --app UUID FLOW_ID (--config JSON | --config-file PATH|-) [--remote-configs JSON] [--expected-updated-at MS]

A freshly created flow has no config until the first flows config update; flows config get returns 404 until then. Pass --expected-updated-at (the updated_at from a prior config get) to fail instead of overwriting a concurrent dashboard edit.

Access Levels

adapty access-levels list --app UUID [--page N] [--page-size N]
adapty access-levels get --app UUID ACCESS_LEVEL_ID
adapty access-levels create --app UUID [flags]
adapty access-levels update --app UUID ACCESS_LEVEL_ID [flags]

Migrations

Manage migrations into Adapty: catalog, transactions and store events. The server provides the steps, available actions and input schemas; use the current response to choose what to do next.

Start or find a migration

adapty migrations list
adapty migrations create --name "Acme Fitness" --json

--name starts a catalog migration into a new Adapty app. For an existing app, choose a flow and its app from list and pass both flags (replace FLOW and APP_ID with those values):

adapty migrations create --flow FLOW --app APP_ID --json

These are alternative creation modes: --name cannot be combined with --flow or --app. Creation starts the flow and saves it as current; the returned JSON contains its ID in migration.id. Use --no-select to create without changing the saved selection. If creation succeeds but saving fails, the command still succeeds and prints a warning with the explicit continuation command.

Commands choose a migration in this order: -m, --migration, non-empty ADAPTY_MIGRATION, then the saved selection for the current token. Explicit IDs do not change the saved selection. Replace mig_7x2 below with an ID from create or list; agents should pass -m explicitly.

Manage a saved selection

adapty migrations use mig_7x2
adapty migrations current --json
adapty migrations unuse

use verifies access through the API, then saves the returned ID for the current token. current reads the effective selection locally: ADAPTY_MIGRATION takes precedence over the saved context. unuse removes the saved selection without authentication; an environment override must be unset in your shell. Changing tokens makes the previous token's selection inapplicable.

The selection is shared across terminals and survives restarting the CLI. After use or create, you can run adapty migrations status, steps, show, run or close without -m. An operation keeps its initially selected ID even if another terminal changes the selection. run and close print the target on stderr before a mutation that uses saved selection. Scripts should pass explicit IDs and use create --no-select to preserve the shared default.

Inspect and run an action

adapty migrations status -m mig_7x2 --json
adapty migrations steps -m mig_7x2
adapty migrations show -m mig_7x2

steps shows the checklist. show lists readable resource names; pass one as RESOURCE to read its data. Choose ACTION_ID from next_actions or available_actions in the status response. For an input action, read its reads resources and prepare a JSON object matching input_schema:

adapty migrations show RESOURCE -m mig_7x2
adapty migrations run ACTION_ID -m mig_7x2 --input-file ./decisions.json --json

Alternatively, use --input-file - with stdin (< ./decisions.json), or --input '{}' if the schema allows an empty object. Omitting input also sends {}. Use only one input option.

Read the action's full confirm text in status or status --json before adding --yes. Input actions requiring confirmation exit 6 without it; there is no interactive prompt. The CLI reads status before refusing, but does not send the action request. After an action, check status again. After revision_conflict, read status and review the current action, input and confirmation before retrying.

For an external action, complete the step in the browser, then check status:

adapty migrations run ACTION_ID -m mig_7x2 --no-browser

--no-browser prints the action details and link. By default, the browser opens in an interactive terminal; pipes and --json require --open. BROWSER=none disables opening, and only HTTPS links are supported. External actions do not send an action request; their JSON response reflects the migration before the browser step. Passing --input or --input-file to an external action returns a usage error (exit 2). File uploads are marked unsupported in status: use the dashboard or an offered Cloud Export action. list --all is also unsupported.

Wait for a change or close a migration

adapty migrations status -m mig_7x2 --wait --timeout 5m --json

--wait returns when the revision changes, the state is no longer running, or the polling budget cannot accommodate another pause. --timeout requires --wait: default 120s, range 1–600s, with formats such as 300, 300s or 5m. In-flight requests and retries may take longer. Exit 0 means the request succeeded, even when the migration is still running or has failed; inspect migration.state. Progress goes to stderr; Ctrl+C exits 130.

To permanently mark a migration as completed:

adapty migrations close -m mig_7x2 --outcome finish --yes

To abandon it instead:

adapty migrations close -m mig_7x2 --outcome cancel --yes

Both outcomes require --yes; there is no confirmation prompt.

JSON output

With --json, list returns {items, available}. All other migration commands return the full migration response (the envelope):

| Data | JSON field | | --- | --- | | Migration ID, state and revision | migration | | Next and optional actions | next_actions, available_actions | | Checklist | steps | | Readable resource names | resources | | Data from show RESOURCE | result (may be null) |

Without --json, show RESOURCE prints just the resource data as JSON, or a message if empty. Wizard Service errors preserve detail, fields, next_step, request_id, retryable and retry_after_seconds under error in JSON when supplied. Human output includes details, field errors, the next step and request ID. Retry metadata in the response does not change automatic retry behavior.

To extract specific fields from the full response, these examples require the separate jq utility:

adapty migrations steps -m mig_7x2 --json | jq '.steps'
adapty migrations show RESOURCE -m mig_7x2 --json | jq '.result'

Apple Search Ads

Apple Search Ads commands live under adapty asa and talk to the ASA service rather than the Developer API. They take no --app: the scope is the company behind your token. A connected Apple Ads account and an active Ads Manager subscription are required — adapty asa whoami tells you where you stand.

adapty asa whoami                      # company, how access was granted, Apple connection state
adapty asa connect [--no-wait]         # link an Apple Search Ads account
adapty asa apps list                   # apps promoted in Apple Search Ads
adapty asa orgs list                   # Apple Search Ads organizations

Every list takes scope filters, and they narrow the query rather than the printed page — asa keywords list unfiltered pages through the whole account, while one ad group is a handful of rows, so scope the read:

adapty asa campaigns list --app APP_UUID --status PAUSED
adapty asa ad-groups list --campaign CAMPAIGN_UUID
adapty asa keywords list --ad-group AD_GROUP_UUID --status ACTIVE
adapty asa keywords list --ad-group AD_GROUP_UUID --ad-group OTHER_UUID   # repeatable
adapty asa creatives list --app APP_UUID

--campaign-group, --app, --campaign, --ad-group are repeatable and take the UUIDs printed by the matching list command; --search matches names case-insensitively. Each list accepts only the filters that make sense for it: --ad-group starts at keywords, negative keywords, search terms and ads, --status is ENABLED/PAUSED everywhere except keywords, which are ACTIVE/PAUSED, and asa ads list has no --app because ads hang off ad groups. An id belonging to another company simply matches nothing.

Campaign structure. These lists return metadata only — numbers come from asa metrics, and only asa search-terms list takes --date-from / --date-to (default: today):

adapty asa campaigns list
adapty asa campaigns get CAMPAIGN_ID
adapty asa campaigns create --org UUID --name "Winter push" --adam-id 123456 --country US --daily-budget 50
adapty asa campaigns create --org UUID --name "LOC push" --adam-id 123456 --country US --daily-budget 50 \
  --invoice-advertiser "Acme Inc" --invoice-order-number PO-42 --invoice-contact-name "Jane Doe" \
  --invoice-contact-email [email protected] --invoice-billing-email [email protected]   # payment_model LOC
adapty asa campaigns update CAMPAIGN_ID [--status PAUSED] [--daily-budget 80] [--country US]

adapty asa ad-groups list
adapty asa ad-groups get AD_GROUP_ID
adapty asa ad-groups create --campaign UUID --name "Brand terms" --default-bid 1.20
adapty asa ad-groups create --campaign UUID --name "Automated Max Conv" --automated   # Max Conversions campaigns
adapty asa ad-groups update AD_GROUP_ID [--default-bid 1.50] [--status PAUSED]

adapty asa ads list
adapty asa ads get AD_ID
adapty asa ads create --ad-group UUID --creative-id 4321 --name "Summer ad"
adapty asa ads update AD_ID [--name "..."] [--status PAUSED]

Bulk structure creation submits a whole tree — campaigns with nested ad groups, keywords, negative keywords and ads — in one call. The input is a JSON structure (the natural path for an AI agent: generate it, pipe it in), or a native Apple Ads template converted server-side. The whole payload is validated before anything is created — a rejection lists every bad node; on acceptance the command polls progress and prints the final report (success / partial / failed, with per-object failures):

adapty asa campaigns bulk-create --file structure.json          # JSON structure, waits and reports
cat structure.json | adapty asa campaigns bulk-create --file -  # same, from stdin
adapty asa campaigns bulk-create --file structure.json --no-wait
adapty asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567
adapty asa campaigns bulk-create --from-file keywords_template.csv --org-id 1234567 --preview
adapty asa campaigns bulk-status OPERATION_ID

The structure is {campaign_group_id | campaign_group_internal_id, campaigns: [...]} where each campaign node either creates (payload) or addresses an existing campaign by id (an anchor, optionally carrying update_payload), and nests ad_groups with keywords, negative_keywords and ads the same way. --from-file takes the native Apple Ads templates and needs --org-id (the Apple org id from asa orgs list); --preview prints the converted request without creating anything. Conversion issues are reported with their sheet, row and column.

Keywords are always applied as a batch, at most 100 per call, and a partial rejection is reported per item:

adapty asa keywords list
adapty asa keywords recommend --adam-id 1668337467 --type brand|generic|competitor [--country US...]
adapty asa keywords add --ad-group UUID --text "running shoes" --text "trail shoes" [--bid 1.20] [--match-type EXACT]
adapty asa keywords add --ad-group UUID --from-file keywords.txt
adapty asa keywords update KEYWORD_ID [KEYWORD_ID...] [--bid 2.00] [--status PAUSED]

adapty asa negative-keywords list
adapty asa negative-keywords add --ad-group UUID --text free
adapty asa negative-keywords add --campaign UUID [--all-ad-groups] --text free

adapty asa search-terms list [--date-from ... --date-to ...]

Product pages and rule-based automations:

adapty asa product-pages list
adapty asa product-pages sync [--adam-id 123456]

adapty asa automations list
adapty asa automations get AUTOMATION_ID
adapty asa automations create --file rule.json [--run-now] [--target-ad-group UUID ...]
adapty asa automations update AUTOMATION_ID [--stop] [--start] [--name "..."] [--file rule.json] [--target-ad-group UUID ...]
adapty asa automations run AUTOMATION_ID [--dry-run]
adapty asa automations runs AUTOMATION_ID

A rule file carries the whole rule: name, status (1 active, 0 stopped), operate_with (what the rule iterates over), apply_to (where it looks), exactly one condition, exactly one action and a run_frequency. The API stores one action and one condition per rule and rejects anything else.

| Field | Shape | | --------------- | ------------------------------------------------------------------------------------------- | | operate_with | search-term, targeting-keyword, campaign, ad-group | | apply_to[] | {"internal_id": UUID, "type": "campaign-group" \| "app" \| "campaign" \| "ad-group" \| "targeting-keywords"} | | conditions[0] | {"operator": ..., "args": number, "operand": {...}}, or {"operator": "and" \| "or", "args": [condition, ...]} | | operator | eq, neq, gt, gte, lt, lte for a leaf; and, or to nest | | operand.field | a metric name in dashboard nomenclature — the same vocabulary asa metrics --metric takes | | date_range_type | today, yesterday, last_1_d, last_3_d, last_7_d, last_14_d, last_28_d, last_30_d, last_60_d, last_90_d, custom | | run_frequency | {"type": "daily", "hour": 8}, {"type": "hour", "value": 24, "start_time": 8}, {"type": "weekly", "weekdays": ["monday"], "hour": 8}, {"type": "monthly", "days": [1], "hour": 8}, {"type": "once", "date_time": "2026-10-01T09:00:00Z"} — hour and start_time are UTC |

The add-as-keyword-to action promotes what the rule found into a keyword in one or more target ad groups. Its params differ by operate_with, and the API picks the variant by shape without a discriminator: a key that belongs to another variant makes it silently choose that variant and drop the rest, which is how a rule ends up as "Add as keyword to 0 ad groups". negate and skip_enable_duplicate_keywords exist only on a search-term rule, pause_in_original_ad_group only on a targeting-keyword one, and targets holds internal_ids — never ids.

A full search-term rule — every term with 10+ taps over the last week becomes an exact keyword in the target ad group at the term's own CPT, and is negated in the ad group it came from:

{
  "name": "Search term harvester",
  "status": 1,
  "operate_with": "search-term",
  "apply_to": [{"internal_id": "CAMPAIGN_UUID", "type": "campaign"}],
  "conditions": [
    {
      "operator": "gte",
      "args": 10,
      "operand": {
        "field": "taps",
        "field_type": "base_field",
        "date_range_type": "last_7_d",
        "date_range_size": 0,
        "date_range_offset": 0,
        "by_days": null
      }
    }
  ],
  "actions": [
    {
      "type": "add-as-keyword-to",
      "params": {
        "targets": {"type": "ad-group", "internal_ids": ["AD_GROUP_UUID"]},
        "cpt_bid": {"type": "search_term_current_cpt", "value": null},
        "match_type": "EXACT",
        "negate": {"enabled": true, "type": "ad-group"},
        "skip_enable_duplicate_keywords": false
      }
    }
  ],
  "run_frequency": {"type": "daily", "hour": 8}
}

The same action on a targeting-keyword rule — a broad keyword that has earned installs graduates into the exact ad group at its current bid and is paused where it was:

{
  "name": "Graduate broad keywords to exact",
  "status": 1,
  "operate_with": "targeting-keyword",
  "apply_to": [{"internal_id": "SOURCE_AD_GROUP_UUID", "type": "ad-group"}],
  "conditions": [
    {
      "operator": "gte",
      "args": 3,
      "operand": {
        "field": "total_installs",
        "field_type": "base_field",
        "date_range_type": "last_14_d",
        "date_range_size": 0,
        "date_range_offset": 0,
        "by_days": null
      }
    }
  ],
  "actions": [
    {
      "type": "add-as-keyword-to",
      "params": {
        "targets": {"type": "ad-group", "internal_ids": ["EXACT_AD_GROUP_UUID"]},
        "cpt_bid": {"type": "keyword_current_bid", "value": null},
        "match_type": "EXACT",
        "pause_in_original_ad_group": true
      }
    }
  ],
  "run_frequency": {"type": "daily", "hour": 8}
}

Rather than hand-writing that params block, pass the action flags — they fill in or override actions[0].params in the file, and the CLI refuses a rule the API would have quietly accepted and broken:

adapty asa automations create --file rule.json --target-ad-group AD_GROUP_UUID \
  --match-type EXACT --cpt-bid-type search_term_current_cpt --negate ad-group
adapty asa automations create --file rule.json --target-ad-group AD_GROUP_UUID \
  --match-type BROAD --cpt-bid-type set_to --cpt-bid 1.50 --no-negate --skip-enable-duplicates
adapty asa automations update AUTOMATION_ID --target-ad-group AD_GROUP_UUID \
  --match-type EXACT --cpt-bid-type search_term_current_cpt

| Flag | Where it lands | | -------------------------- | --------------------------------------------------------------------- | | --target-ad-group | targets.internal_ids, repeatable; a rule without one does nothing | | --match-type | match_type: BROAD or EXACT | | --cpt-bid-type | cpt_bid.type: ad_group_default_bid, set_to, search_term_current_cpt, keyword_current_bid | | --cpt-bid | cpt_bid.value: the bid itself with set_to (required), a percent markup on the entity's own bid with search_term_current_cpt / keyword_current_bid, rejected with ad_group_default_bid | | --negate / --no-negate | negate (search-term rules): ad-group or campaign, or off | | --skip-enable-duplicates | skip_enable_duplicate_keywords (search-term rules) | | --pause-original | pause_in_original_ad_group (targeting-keyword rules) |

--cpt-bid-type and --match-type have no defaults, here or in the API: a bid and a match type are a spend decision and a reach decision, so when params are built from scratch the CLI asks for them instead of guessing. On update, an action flag turns the call into a read-modify-write — the rule is read, actions[0].params is rebuilt and the whole actions list is written back, since the API replaces it wholesale. That also repairs a rule whose stored params carry the wrong shape: only the keys that fit are kept, the strays are dropped, and anything missing has to come from a flag. A dashboard edit made between the read and the write is overwritten.

Metrics take an entity level, a period and an optional metric selection. Rows come back one per entity, aggregated and sorted server-side, so a top-N or a breakdown is a single call — use --order-by with a small --page-size for rankings, metrics overview for account totals and time series, and one big page (up to 1000 rows) when you genuinely need every row; never sum pages client-side:

adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31
adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --order-by spend --page-size 5
adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --group-by country --page-size 1000
adapty asa metrics --entity keyword --date-from 2026-07-01 --date-to 2026-07-31 --metric spend --metric roas
adapty asa metrics --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 --metric roas --by-days 7 --by-days 90
adapty asa metrics overview --entity campaign --date-from 2026-07-01 --date-to 2026-07-31 [--period-unit week]

There is no ltv metric: lifetime value is a cohort metric read at a renewal window, so --by-days is how you ask for day-7 or day-90 values — on either route, up to 16 windows per call. --order-by-day ranks the rows by one of those windows, which is how you get the top campaigns by day-90 ROAS in a single call.

Competitor summary takes 1–5 Apple App Store IDs and covers the last full month across every country — there are no period or country flags on purpose. The first call on a cold cache can take tens of seconds:

adapty asa competitors summary --app-ids 111111111,2222222

Writes go straight to Apple and take seconds, so every writing command first prints the exact request body and asks for a yes. --yes skips the question for scripts; in a pipe or under --json the command refuses instead of waiting for input that will never come. There is no undo — the CLI has no delete.

Every write also carries an idempotency key. The CLI generates one per invocation and retries once on a network error, so a request that died on the wire is never applied twice. Pass --idempotency-key to pin the key yourself: re-running a script with the same key within 24 hours replays the stored result — the CLI prints "Already applied earlier — showing the stored result." — instead of creating a second entity. The same key with a different body is rejected (422 cli_idempotency_key_reuse), and a concurrent duplicate answers 409 cli_idempotency_in_progress.

Analytics is rate limited per company: the metrics routes get 5 calls a minute (at most 2 in any 10 seconds) and share a pool of two concurrent queries with the search-terms list — a busy pool answers 429 cli_analytics_busy, an exhausted window 429 cli_rate_limit_exceeded, both with the exact wait in Retry-After. The CLI absorbs a single 429 on its own — it waits the announced Retry-After (up to 60 seconds; cool-downs are never waited out) and retries once — so a 429 that reaches you means the retry failed too. A burst of 429s puts the token into an escalating cool-down (cli_cooldown_active, 5 minutes → 30 minutes → 3 hours); retries during the pause don't extend it, but the cure is fixing the failing request, not waiting out the pause in a loop. An automation run is queued rather than awaited: run prints a run ID and the outcome shows up in adapty asa automations runs.

Attribution

Attribution analytics across ad networks live under adapty attribution and talk to the attribution service rather than the Developer API, with the same token. report and values take --app; the two catalogs take no app.

adapty attribution metrics       # metric names a report can ask for
adapty attribution dimensions    # dimensions a report can group or filter by
adapty attribution values --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --dimension campaign
adapty attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 \
  --metrics spend,installs,d7_roas --group-by date,campaign --granularity week \
  --filter country=US,GB --revenue-basis proceeds --sort spend:desc

Dates are inclusive days in the app's timezone. --metrics and --group-by take repeated flags or comma-separated lists; --filter dimension=value[,value] repeats, where one value matches exactly and several match any of them; --sort field[:asc|desc] is ascending unless told otherwise. --json prints the service's answer unchanged ({"success", "data", "meta"}). In the human view a metric that cannot be computed prints —, never 0.

A rejected request exits 4 and carries the service's error_code in the --json error, e.g. attribution_unknown_metric or attribution_access_required (the company has no attribution access — logging in again does not help). report and values are sent once and never retried: on attribution_busy (429) or an unavailable service (503), wait for the Retry-After the service sent before running the query again. The --json error carries that wait as retry_after_seconds, whenever the service sent one.

Global Flags

| Flag | Description | | ------------- | -------------------------------------- | | --json | Output as JSON | | --help | Show help | | --page | Page number (default: 1) | | --page-size | Items per page (default: 20, max: 100; asa commands: default 100, max 1000) |

Paywall Preview

adapty flows config preview <config_file> turns a local flow config into a render URL. On a TTY it opens the browser; piped or with --json it prints the URL alone. Screenshotting is the caller's job — the CLI only builds the URL.

Small configs only. This is a quick-look escape hatch. The config travels in the URL fragment, and past roughly 32KB of pretty-printed JSON the render page becomes slow and unreliable. Trim to the screen you care about, or preview the saved flow in the dashboard builder instead.

That URL is huge. Even a config the page renders happily produces thousands of characters, and a 668KB flow yields ~113,000. Nobody should read it — agents especially should never let it into their context. Pipe it into whatever captures the screenshot:

adapty flows config preview flow.json --screen scr_abc | node capture.mjs --out shot.png

# or, for a tool that wants a flag instead of stdin:
node capture.mjs --url "$(adapty flows config preview flow.json --screen scr_abc)" --out shot.png

Prefer the pipe: it has no size limit, while an argument is capped by the shell (~1MB, so a config around 6MB).

See skills/adapty-cli/references/cli-commands.md for the flags and the size ceiling.

Environment Variables

| Variable | Description | | -------------------- | --------------------------------------------------------------------------------------- | | ADAPTY_TOKEN | Override stored auth token | | ADAPTY_API_URL | Override Developer API base URL (default: https://api-admin.adapty.io/api/v1/developer) | | ADAPTY_ASA_API_URL | Override Apple Search Ads base URL (default: https://api-asa-admin.adapty.io/api/v1/cli) | | ADAPTY_MIGRATION | Migration id used by adapty migrations when -m is not given | | ADAPTY_ATTRIBUTION_API_URL | Override attribution base URL (default: https://api-ua.adapty.io/api/v1/cli) | | ADAPTY_APP_URL | Override dashboard base URL (default: https://app.adapty.io). Used by flows config preview for the fixed /flow-preview route, and by auth login to keep the verification link on that host |

The API URLs are independent: pointing ADAPTY_API_URL at a staging host leaves adapty asa and adapty attribution on their own defaults, and the other way round.

Claude Code Skill

Install the Adapty CLI skill for Claude Code:

npx skills add adaptyteam/adapty-cli --skill adapty-cli

Development

pnpm install
pnpm build
./bin/run.js apps list

License

MIT