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 adaptyRequires Node.js 22 or 24. Node 18 and 20 are past end-of-life and are not supported.
Authentication
adapty auth loginOpens 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 listOther 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 selectionauth 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 --jsonThese 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 unuseuse 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_7x2steps 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 --jsonAlternatively, 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 --yesTo abandon it instead:
adapty migrations close -m mig_7x2 --outcome cancel --yesBoth 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 organizationsEvery 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_IDThe 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_IDA 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,2222222Writes 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:descDates 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.pngPrefer 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-cliDevelopment
pnpm install
pnpm build
./bin/run.js apps listLicense
MIT
