Echo/Docs

CLI command

expansion

Expansion offers: one upgrade offer, through a lifecycle sequence, to each person whose use of a metered feature reached the share of their plan's limit the operator states — or, cross-selling, an adjacent feature offered to such people who do not use it yet.

Invocation#

Shell
echo-cli expansion list --product <id>
echo-cli expansion add --product <id> --name <text> --app <web or mobile source> --usage-event <event> --window-days <days> (--limit <uses> | --plan-billing revenuecat|stripe --plan-app <revenuecat app|web source> --plan-limits <product or price=uses,...>) --share <fraction> --offer-trigger <trigger-id> [--cross-sell-event <event>] --by <name>
echo-cli expansion remove --rule <rule-id>
echo-cli expansion run --product <id>
echo-cli expansion offers --rule <rule-id>

Inputs and output#

list writes the product's rules by name: id, product_id, name, app_id, usage_event, window_days, plan_limit (null for a rule with a limit per plan), plan_billing, plan_app_id and plan_limits (null for a rule with one limit), share, offer_trigger_id, cross_sell_event (null for an upgrade offer), created_by and created_at; add and remove write the rule. run writes runs, one per rule: rule_id, name, watched (people with any use in the window), unplanned (those whose plan the rule does not list: no active subscription billed through plan_app_id, or one on a plan plan_limits does not name; always 0 with one limit) and offered (the offers it made now). offers writes the rule's offers, newest first: id, rule_id, subject, uses, plan_limit (the limit the person was held to), plan_product_id (the billed plan — RevenueCat product or Stripe price — it came from, or null), share, cross_sell_event, window_start, enrollment_id and offered_at.

State effect#

Every verb calls Echo Web's authenticated /api/analytics/expansion with ECHO_INTERNAL_BEARER at ECHO_WEB_BASE_URL; the Expansion offers section of the tracking screen and of Echo Desktop's Contact frequency screen call the same operations, and echo-mcp reads rules and offers as echo_expansion_offers. A run counts each person of the rule's source (every identifier the identity edges join) by the usage events stored in the last --window-days days — a web source's web events or a mobile app's mobile events; a person whose count reached --share times --limit gets one offer, stored in expansion_offers with the count, and is enrolled once in the sequence of the --offer-trigger lifecycle trigger, which email-sequencing-cli reads as it reads every lifecycle enrolment. With --cross-sell-event, the event of the adjacent feature the offer sells, a person who sent that event in the same window is not offered: the offer goes to people who lean on the watched feature and have not taken up the adjacent one. A person offered before is never offered again by the same rule. /api/cron/expansion runs every product's rules daily.

Refusals#

  • A wrong invocation is refused before anything is sent: "expansion takes <every verb's usage, separated by semicolons>; got <word>", "expansion <verb> does not take <flag>; it takes …", "<flag> needs a value" and "expansion <verb> needs <flag>".
  • Echo Web answers a bad request with "product_id is required: …", "name is required: …", "app_id is required: …", "usage_event is required: …", "window_days must be a positive whole number: …", "plan_limit must be a positive whole number: …", "share must be above 0 and at most 1: the fraction of the plan's limit a person must have used; got <value>", "cross_sell_event must be another feature's event than usage_event <event>: the offer sells the adjacent feature to people who use this one" or "lifecycle trigger <id> belongs to product <other>, not <product>; a rule offers through its own product's sequence"; not found with "no product has id <id>; echo-cli products lists the ones that exist" or "no expansion rule has id <id>; echo-cli expansion list --product <id> lists them"; and a conflict with "product <id> already has the expansion rule <name> as <id>". The command prints Echo Web's sentence and exits 1.
  • --plan-limits takes <plan>=<uses> pairs separated by commas, with --plan-billing naming where those plans are billed and --plan-app what bills them. With revenuecat, each plan is a RevenueCat product id of the app --plan-app (as its billing events name it), and each person is held to the limit of the product of their newest stored RevenueCat event of that app whose subscription has not expired. With stripe, each plan is a Stripe price id and --plan-app the web source whose events the Stripe webhook records (the checkout carrying the user's id as userId in the Stripe metadata), and each person is held to the limit of the price of their newest subscription whose latest recorded event is not its cancellation (subscription_cancelled, which the webhook records when Stripe deletes it). Either way the person is joined to their plan through the identity edges. A malformed pair is refused before anything is sent with "--plan-limits takes <product id or price id>=<uses> pairs separated by commas; got <pair>"; Echo Web refuses "state exactly one of plan_limit (…) or plan_limits (…, with plan_billing and plan_app_id)", "plan_billing is required: where the plans of plan_limits are billed, one of revenuecat, stripe", "plan_billing must be one of revenuecat, stripe: …; got <value>", "plan_app_id is required: …", "plan_limits.<plan> must be a positive whole number: …" and "plan_limits names no plan: …".