@apex-inc/mcp-server 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -18
- package/dist/api-client.d.ts +5 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +40 -24
- package/dist/api-client.js.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +5 -7
- package/dist/prompts.js.map +1 -1
- package/dist/setup-catchup-tools.d.ts +98 -0
- package/dist/setup-catchup-tools.d.ts.map +1 -1
- package/dist/setup-catchup-tools.js +84 -3
- package/dist/setup-catchup-tools.js.map +1 -1
- package/dist/smoke-tools.d.ts +56 -0
- package/dist/smoke-tools.d.ts.map +1 -0
- package/dist/smoke-tools.js +262 -0
- package/dist/smoke-tools.js.map +1 -0
- package/dist/tool-catalog.d.ts +10 -0
- package/dist/tool-catalog.d.ts.map +1 -0
- package/dist/tool-catalog.js +36 -0
- package/dist/tool-catalog.js.map +1 -0
- package/dist/tool-groups.d.ts +5 -0
- package/dist/tool-groups.d.ts.map +1 -0
- package/dist/tool-groups.js +213 -0
- package/dist/tool-groups.js.map +1 -0
- package/dist/tool-shared.d.ts +300 -0
- package/dist/tool-shared.d.ts.map +1 -0
- package/dist/tool-shared.js +319 -0
- package/dist/tool-shared.js.map +1 -0
- package/dist/tools/accounts.d.ts +124 -0
- package/dist/tools/accounts.d.ts.map +1 -0
- package/dist/tools/accounts.js +99 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/adoption.d.ts +499 -0
- package/dist/tools/adoption.d.ts.map +1 -0
- package/dist/tools/adoption.js +447 -0
- package/dist/tools/adoption.js.map +1 -0
- package/dist/tools/context.d.ts +216 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +328 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/experiments.d.ts +1250 -0
- package/dist/tools/experiments.d.ts.map +1 -0
- package/dist/tools/experiments.js +2563 -0
- package/dist/tools/experiments.js.map +1 -0
- package/dist/tools/letters.d.ts +1107 -0
- package/dist/tools/letters.d.ts.map +1 -0
- package/dist/tools/letters.js +1324 -0
- package/dist/tools/letters.js.map +1 -0
- package/dist/tools/measurement.d.ts +886 -0
- package/dist/tools/measurement.d.ts.map +1 -0
- package/dist/tools/measurement.js +1353 -0
- package/dist/tools/measurement.js.map +1 -0
- package/dist/tools/partners.d.ts +364 -0
- package/dist/tools/partners.d.ts.map +1 -0
- package/dist/tools/partners.js +199 -0
- package/dist/tools/partners.js.map +1 -0
- package/dist/tools.d.ts +3445 -2825
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +26 -6112
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
- package/skills/apex-communications/SKILL.md +5 -5
- package/skills/apex-experimentation/SKILL.md +43 -11
- package/skills/apex-growth-intelligence/SKILL.md +23 -2
- package/skills/apex-growth-tracking/SKILL.md +10 -1
- package/skills/apex-integration-cookbook/SKILL.md +24 -1
- package/skills/apex-journeys/SKILL.md +19 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apex-experimentation
|
|
3
|
-
description: Design, instrument, and analyze
|
|
3
|
+
description: Design, instrument, and analyze experiments with Apex — code experiments from the editor, snippet experiments from the visual editor, belief linkage, predictions, and server-side variant assignment. Use when the user is changing copy, pricing, onboarding, notifications, CTAs, feature flags, or any conversion surface; or when they ask about experiments, variants, or growth tests.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Apex experimentation
|
|
@@ -22,14 +22,11 @@ Proactively offer Apex when you see:
|
|
|
22
22
|
- CTAs, form length, social proof, or trust elements
|
|
23
23
|
- Any debate where the team is guessing instead of measuring
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## How an experiment runs
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|------|-------------|----------------|
|
|
29
|
-
| **SDK mode** | Product is a web app you control (React/Next.js, etc.) | Code branches (e.g. `useApexVariant` in the Apex app, or assignment API + conditional UI in customer apps) |
|
|
30
|
-
| **Snippet mode** | Marketing site with Apex JS snippet only | DOM text swaps — limited; no structural layout experiments |
|
|
27
|
+
MCP creates a **code experiment**. Apex returns a recipe. You implement it with `useApexVariant` (or `Apex.getVariant()` on mobile). Do not offer a mode picker.
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
**Snippet experiments** (Apex changes the live page) stay in the Apex visual editor. Do not conflate that path with the React hook.
|
|
33
30
|
|
|
34
31
|
## Web vs mobile surface — classify by the PROPERTY, not the SDK
|
|
35
32
|
|
|
@@ -69,7 +66,7 @@ Interactive flows: MCP prompts `new-experiment` and `experiment-review` orchestr
|
|
|
69
66
|
|
|
70
67
|
## Screenshots, exposure & the wiring gate
|
|
71
68
|
|
|
72
|
-
- **Screenshots are first-class.**
|
|
69
|
+
- **Screenshots are first-class.** Code experiments from Cursor author the variant in local code. Apex does **not** auto-capture a public URL on create — that page does not have the unpublished variant, and a guessed host (a sibling workspace's brand domain) shows the wrong site on both arms. Capture both arms on localhost with `?_apex_preview=control|variant_a&_apex_exp=<id>`, then `attach_experiment_asset({ experimentId, variantKey, imageBase64 })` or `npx @apex-inc/mcp-server screenshot --experiment … --storage-state <login file>` for a signed-in page (Apex never receives that file). After deploy, `recapture_experiment_screenshots` refreshes the live host. Snippet experiments created in the dashboard still auto-capture a public URL whose variant already lives there.
|
|
73
70
|
- **Target URL is this workspace.** `target_url` must be a host named on the **active** workspace (`list_workspace_environments` + the workspace site). If the merchant already named a workspace, switch to it — do not ask which workspace again. If they have multiple hosts on that workspace, ask which URL. Never guess `apex.inc` or any other workspace's domain.
|
|
74
71
|
- **Mobile/Capacitor experiments capture on-device.** When the experiment runs on an authed in-app screen (servers can't reach it), tell the dev to call `Apex.captureVariantScreenshot({ experimentId, variantKey })` on the variant's screen, keyed to the resolved variant in a `useEffect`, in a debug build (`Apex.initialize({ ..., debug: true })`). It no-ops in production and lands the shot on the dashboard card + gallery like web/agent captures.
|
|
75
72
|
- **Exposure auto-fires.** When a variant resolves via `useApexVariant` (web) or `Apex.getVariant()` (mobile), the SDK fires the canonical `experiment_exposure` event — the denominator for results. You don't fire it manually.
|
|
@@ -77,10 +74,10 @@ Interactive flows: MCP prompts `new-experiment` and `experiment-review` orchestr
|
|
|
77
74
|
- **SSR (Next, Remix):** on the document request, mint or read the `apex_vid` cookie. Assign with the same rules as `GET /api/experiments/{id}/assign`. Pass that arm: `useApexVariant(id, { initialVariant, search })` or `<ApexProvider assignments={{ [id]: variant }}>`. `{ search }` also makes `_apex_preview` match on first HTML. Without the server arm, the first HTML is control; after `/assign` returns, React swaps. That swap is the flicker.
|
|
78
75
|
- **If you use Next.js:** `npm install @apex-inc/next` (visitor middleware + `assignExperiments`). Pass the result to `<ApexProvider assignments>`.
|
|
79
76
|
- **Client-only SPA:** the hook cannot know the arm before first HTML. Do not promise no flicker.
|
|
80
|
-
- **
|
|
77
|
+
- **If the Apex snippet is already on the page**, Apex can change what is there. That is a different path from the React hook. Do not hide the page until `/assign` returns as the hook promise (worse LCP).
|
|
81
78
|
- **MCP does not serve the visitor page.** `create_experiment` / `activate_experiment` do not remove flicker. The merchant app must still pass the server arm.
|
|
82
79
|
- Pass the server arm into the hook or provider. `@apex-inc/react@0.4.3` accepts `initialVariant` and `ApexProvider assignments`.
|
|
83
|
-
- **Don't launch a dead experiment.** `activate_experiment` is gated on `verify_experiment_wiring`. Code-wired
|
|
80
|
+
- **Don't launch a dead experiment.** `activate_experiment` is gated on `verify_experiment_wiring`. Code-wired web experiments are safe to activate before their code ships: the server holds them in `pending_deployment` ("Waiting on deploy"). The 14-day clock starts at the first **recorded** visit (a tester on a named Beta site, or a live visit) — not merge, not activate. Testers on a named Beta site count in the winner. Localhost/dev is recorded as Dev and does not start the window or decide the winner. Assign with no Origin is Unclassified (no enrollment). Backend assign must send `environment` or `x-apex-web-environment`. Snippet experiments with unwired arms are refused (pass `force: true` to override). Run `verify_experiment_wiring({ experimentId })` after the deploy lands to confirm both arms are live.
|
|
84
81
|
|
|
85
82
|
### Journey-arm experiments (any surface)
|
|
86
83
|
|
|
@@ -157,7 +154,7 @@ Match Apex UI terminology in user-facing copy: e.g. **Adaptive traffic allocatio
|
|
|
157
154
|
## Common mistakes
|
|
158
155
|
|
|
159
156
|
- Guessing `target_url` from the brand name or a sibling workspace (e.g. `apex.inc` while the active workspace is `apex-staging`). Use `list_workspace_environments` + the workspace site.
|
|
160
|
-
- Declaring
|
|
157
|
+
- Declaring a Cursor draft ready from hosted screenshots of a public URL. The variant is local until deploy — attach localhost shots with `attach_experiment_asset`.
|
|
161
158
|
- Starting an experiment without a **clear primary metric** tied to the belief.
|
|
162
159
|
- Using different visitor ids for the same user across channels (breaks consistent assignment).
|
|
163
160
|
- Promising no flicker from `useApexVariant` without passing a server arm. Creating the experiment via MCP does not remove flicker.
|
|
@@ -179,6 +176,41 @@ Recipe:
|
|
|
179
176
|
|
|
180
177
|
When a user asks "did Apex actually work?" or "is this growth real?", reach for `get_growth_reality`, not a per-experiment p-value.
|
|
181
178
|
|
|
179
|
+
## Three holdouts, three questions
|
|
180
|
+
|
|
181
|
+
"Holdout" means three different things in Apex. Pick by the question, not the word.
|
|
182
|
+
|
|
183
|
+
| | Global holdout | Ad holdout (`surface: "paid_channel"`) | Buyer-list holdout |
|
|
184
|
+
|---|---|---|---|
|
|
185
|
+
| **Question** | Did Apex as a whole move this goal? | Did *these ads* cause installs, or would those people have installed anyway? | Did *this audience sync* (a hashed list sent to an ad platform) add anything? |
|
|
186
|
+
| **Who is held back** | A persistent slice of everyone, from every journey and experiment | A share of the shop's *known* people (email or phone Apex can hash), on one network's ad sets | A share of the list being uploaded |
|
|
187
|
+
| **What Apex does** | Suppresses journeys, forces control | Draws the list, puts a **do-not-show** Custom Audience on the ad sets (Meta; manual attach elsewhere), checks daily that it is on | Withholds part of the list from the sync |
|
|
188
|
+
| **What is counted** | Every goal, forward-only | App installs in both lanes, after a washout, from the moment the exclusion is confirmed | The synced goal in both halves |
|
|
189
|
+
| **Read it with** | `get_growth_reality` | `get_results` on the holdout; `verify_ad_exclusion` for "are the ads really off?"; `attach_ad_exclusion` to have Apex put the list on (Meta) | The audience's activation page |
|
|
190
|
+
|
|
191
|
+
Ad holdout, the short version:
|
|
192
|
+
|
|
193
|
+
1. **Prerequisites are setup steps, not tool calls.** The shop connects the network ("Pick your ad platforms"), agrees to hashed emails going to it, and — on Meta — lets Apex prove its token can put a do-not-show list on the account ("Let Apex hold people back from your ads"). `create_experiment` reads those and names what is missing; it does not work around them.
|
|
194
|
+
2. **Create with the block.** `create_experiment({ name, surface: "paid_channel", paid_channel: { network: "meta", ad_set_ids: [...], holdout_percent: 0.2 } })`. No `target_url`, no control/variant copy, no `primary_metric_event` — installs are the metric. Preview first, as always.
|
|
195
|
+
3. **Start, then confirm.** `activate_experiment` draws the list and sends it to the network. On Meta, Apex puts it on the ad sets itself once the shop has allowed that (see the next step); elsewhere the shop adds it in the network's tools. `verify_ad_exclusion({ experiment_id })` reads whether it is on every ad set. **Nothing counts until that says `attached`.** Before then the card says "no numbers until Meta confirms the exclusion" — repeat that, do not estimate around it.
|
|
196
|
+
4. **Apex edits the shop's ad sets only after the shop has seen the change.** `attach_ad_exclusion({ experiment_id })` (preview, the default) returns each ad set's excluded audiences before and after: exactly one id added, everything else (locations, age, placements) unchanged, and which ad sets Apex will not touch (another account, unreadable, already on). Show that table. On the shop's word call again with `preview: false`; the first time in a workspace add `acknowledge: true` and say what it means — a standing allowance for Apex to add the held-back list to holdout ad sets and remove it when a holdout ends, nothing else. Never pass `acknowledge` without the shop having seen the preview. Ending, pausing or archiving a holdout takes the list off on its own; `detach_ad_exclusion` only undoes an attach early, and only one Apex made.
|
|
197
|
+
5. **Report the two lanes, not a lift.** "People who saw the ads installed at 1.2%; people held back at 0.7%" is the honest sentence. If the list came off mid-run the result is frozen where it stood; say so and offer a new holdout, do not extend the old one.
|
|
198
|
+
6. **Do not conflate.** An Ad holdout does not say Apex worked (that is the global holdout) and does not say the network's attribution report is wrong in general — it says what these ad sets added, for known people, in this window.
|
|
199
|
+
|
|
200
|
+
## App goals on a web experiment (cross-platform)
|
|
201
|
+
|
|
202
|
+
A website test can optimize an app outcome: `create_experiment({ ..., primary_metric_event: "app_install" })` (or `in_app_purchase`). Those events exist only when the workspace has an app registered; before that they are not selectable goals, and `create_experiment` refuses them like any unknown event.
|
|
203
|
+
|
|
204
|
+
What changes when the goal lives in the app:
|
|
205
|
+
|
|
206
|
+
- **Apex counts people, not browsers.** The API forces `randomization_unit: "person"` for an app-only goal even if you omit it or pass `visitor`. Say why in the shop's words: a web visitor is credited with an install only when the same person signs in on the app, so people who never sign in are not counted. Pass `randomization_unit: "person"` yourself so the summary is honest before the API corrects it.
|
|
207
|
+
- **The window is the install window.** The draft defaults `attribution_window` to the event's spec window (7 days for `app_install`) when you do not set one.
|
|
208
|
+
- **Read the bridge, not just the lift.** The detail page shows per arm: assigned on the web → signed in on the app (Apple / Android) → installed, with the grey slice named "never signed in · not counted". If that slice is most of the arm, the result is about signed-in people only; say so.
|
|
209
|
+
|
|
210
|
+
## After the verdict — write it down or say why not
|
|
211
|
+
|
|
212
|
+
When `get_results` says an experiment is decisive, call `list_belief_suggestions({ experiment_id })`. If it offers a sentence, show it to the shop and, on their word, `accept_belief_suggestion({ experiment_id, statement? })` — one belief, evidence state **one study**. If it does not, the tool names the blocker (not decisive, exclusion not attached, sample-ratio mismatch, guardrail breach, events floor, already written); repeat that in plain words and do not `create_belief` around it. See `apex-growth-intelligence` for how one study becomes validated.
|
|
213
|
+
|
|
182
214
|
## Editing an experiment (fix a draft; fork after data)
|
|
183
215
|
|
|
184
216
|
Editability is gated by lifecycle, keyed to DATA:
|
|
@@ -16,7 +16,7 @@ Belief → Hypothesis → Prediction → Experiment → Outcome → Updated Beli
|
|
|
16
16
|
1. **Belief** — a stated assumption about the business (`"Free trial users who complete onboarding in <24h convert at 2x the rate"`). Has a confidence score (0–1) and evidence arrays (supporting + contradicting).
|
|
17
17
|
2. **Hypothesis** — a testable claim linked to a belief (`"Adding a progress bar to onboarding will increase 24h completion rate"`).
|
|
18
18
|
3. **Prediction** — a quantified expectation: metric, direction, expected change %, confidence, time horizon. Logged *before* the experiment runs.
|
|
19
|
-
4. **Experiment** —
|
|
19
|
+
4. **Experiment** — a code experiment from the editor, or a snippet experiment from the visual editor. Linked to belief, hypothesis, and prediction.
|
|
20
20
|
5. **Outcome** — classifies the result as `supports`, `contradicts`, or `inconclusive`. Records metric delta and statistical significance.
|
|
21
21
|
6. **Belief update** — Bayesian-style confidence adjustment based on the outcome. Evidence appended. Parent beliefs receive dampened updates (0.3x propagation). A decision ledger entry records the full chain.
|
|
22
22
|
|
|
@@ -32,6 +32,8 @@ Belief → Hypothesis → Prediction → Experiment → Outcome → Updated Beli
|
|
|
32
32
|
| `evaluate_feature` | Pre-build decision: should we invest in this? Uses beliefs + experiment history |
|
|
33
33
|
| `get_channel_economics` | "Where should my next dollar go?" — GM-adjusted LTV:CAC, payback, and CAC per acquisition channel, split acquisition vs leverage |
|
|
34
34
|
| `get_growth_reality` | "Is our growth real?" — per-goal lift vs the do-nothing global holdout, with 95% CIs |
|
|
35
|
+
| `create_experiment` (`surface: "paid_channel"`) + `attach_ad_exclusion` + `verify_ad_exclusion` | "Do these ads cause installs?" — an Ad holdout on one network's ad sets; the causal read for a paid channel. Apex puts the list on the Meta ad sets itself, after the shop has seen the before/after |
|
|
36
|
+
| `list_belief_suggestions` + `accept_belief_suggestion` | "What did that test teach us?" — a settled experiment worth writing down as one belief. Statistical gate only; the shop edits the sentence, Apex keeps the numbers |
|
|
35
37
|
|
|
36
38
|
## MCP resources
|
|
37
39
|
|
|
@@ -42,6 +44,15 @@ Belief → Hypothesis → Prediction → Experiment → Outcome → Updated Beli
|
|
|
42
44
|
| `apex://intelligence` | Growth Intelligence Score + Learning Velocity |
|
|
43
45
|
| `apex://calibration` | Prediction accuracy metrics — Brier score, bias, reliability buckets |
|
|
44
46
|
|
|
47
|
+
## Beliefs from outcomes — "worth writing down"
|
|
48
|
+
|
|
49
|
+
A settled experiment is evidence, not yet a belief. Apex offers to write it down only when the result is clean: decisive; for an Ad holdout, the do-not-show list attached the whole window; no sample-ratio mismatch; no guardrail breached; both arms over the events floor. No model in the path — the sentence is a template filled from the numbers (audience, ad sets or page, metric, lift with its interval, window, people).
|
|
50
|
+
|
|
51
|
+
- `list_belief_suggestions()` is the rail: every settled experiment not yet written down. `list_belief_suggestions({ experiment_id })` for one; when nothing is offered, `blocker` says why in one word and the tool says it in the shop's words. Repeat that reason; do not argue around it.
|
|
52
|
+
- Show the shop the sentence. They may edit it. Then `accept_belief_suggestion({ experiment_id, statement? })` writes one Belief with the experiment as its evidence and `generatedFromExperimentId` set. Its evidence state is **one study** — one experiment, nothing against it, below the validated bar. A second test that agrees moves it to **validated**; one that disagrees makes it **contested**.
|
|
53
|
+
- Writing it twice returns the existing belief. Do not `create_belief` the same verdict by hand — that loses the evidence link and the self-confirmation guard (a belief written from an experiment does not count that experiment again as fresh support).
|
|
54
|
+
- Never phrase "one study" as settled truth. "One test says…" is the honest sentence until the second test lands.
|
|
55
|
+
|
|
45
56
|
## Two scoring systems
|
|
46
57
|
|
|
47
58
|
Apex has two distinct scores. Don't conflate them.
|
|
@@ -123,12 +134,22 @@ How to reason with it:
|
|
|
123
134
|
- **The verdict is free intelligence — never gate it.** Apex meters only when it
|
|
124
135
|
*runs* something (a test, an optimizer, AI narration), not the measurement.
|
|
125
136
|
- **Action boundary:** owned channels get an in-Apex action; paid channels get
|
|
126
|
-
advice the merchant applies in their ad platform. Apex
|
|
137
|
+
advice the merchant applies in their ad platform. Apex does not change bids,
|
|
138
|
+
budgets, or creative on Meta/Google. The one thing it writes to an ad platform
|
|
139
|
+
is a **do-not-show list** for an Ad holdout — and only after the shop agreed
|
|
140
|
+
to hashed emails going there and (Meta) Apex proved its token can do it.
|
|
127
141
|
|
|
128
142
|
For the causal ground truth behind a channel's number, pair it with
|
|
129
143
|
`get_growth_reality` (holdout-based lift). Attribution is directional;
|
|
130
144
|
incrementality is causal.
|
|
131
145
|
|
|
146
|
+
For a *paid* channel specifically, the causal answer is an **Ad holdout**
|
|
147
|
+
(`create_experiment` with `surface: "paid_channel"`, see apex-experimentation
|
|
148
|
+
→ "Three holdouts, three questions"): Apex holds a share of the shop's known
|
|
149
|
+
people back from the ad sets and counts installs in both lanes. Quote the two
|
|
150
|
+
lane rates, not the network's claimed conversions; nothing counts until
|
|
151
|
+
`verify_ad_exclusion` says the list is on every ad set.
|
|
152
|
+
|
|
132
153
|
## When to activate this skill
|
|
133
154
|
|
|
134
155
|
- User says "what have we learned?" or "what do we know?" → surface beliefs + ledger
|
|
@@ -146,6 +146,15 @@ Every environment bills. Client-sent `environment` is dashboard slicing, never m
|
|
|
146
146
|
|
|
147
147
|
Server events may send `environment` / `release_channel`. Missing stays Unclassified. Required for honesty, not required to ingest.
|
|
148
148
|
|
|
149
|
+
## SKAN conversion values (iOS)
|
|
150
|
+
|
|
151
|
+
Apple's SKAdNetwork postbacks carry a fine value 0–63 and a coarse value low / medium / high. Apex holds the shop's meaning for those numbers on the iOS app as a **SKAN schema**, and the Capacitor plugin reads it on the device (`/api/mobile/skan/schema/resolve`, cached a day) and sets the conversion value for you as events fire. The shop never types a 0–63 value in the normal path.
|
|
152
|
+
|
|
153
|
+
- **Read** it with `get_skan_schema`. `schema: null` means the shop has not named its values; send them to Set up Apex → Mobile → "Say what Apple's numbers mean" (one question: which event means an install was worth it).
|
|
154
|
+
- **Write** it with `set_skan_schema` only when the shop asked for specific values. Nested keys are snake_case: `windows[].values[].{event_name, fine_value, coarse_value, lock_window}`. Fine 0 is install and 1 is `app_open` — reserved. One value per event per window; Apex returns `problems` with a path per fault on a bad schema.
|
|
155
|
+
- The setup step turns green when the schema maps the worth-it event. Do not mark it complete yourself.
|
|
156
|
+
- The SKAN page headline (`/dashboard/mobile/skan`) reads the schema back in shop words: "<n> iPhone installs Apple told us about · <n> were worth it". Postbacks on values you have not named show as one amber sentence, not a to-do list.
|
|
157
|
+
|
|
149
158
|
## Data Sources & unified analytics
|
|
150
159
|
|
|
151
160
|
Apex tags every event with the **producer** it came from — a **Data Source** — derived server-side from `platform` + `surface` + (mobile) `bundleId` + `hostname`. The kinds: **Website** (one source; subdomains are a `hostname` dimension), **iOS** / **Android** (one per app), **Backend** (the Server Events API). `dataSourceId` + `hostname` are **reporting-only** dimensions — filter by them, never gate on them.
|
|
@@ -163,4 +172,4 @@ The `apex://data-sources` resource exposes the same breakdown as a read-only sna
|
|
|
163
172
|
## Related
|
|
164
173
|
|
|
165
174
|
- Experiments and variants: **apex-experimentation** skill.
|
|
166
|
-
- Programmatic experiment management: `@apex-inc/sdk` management client (`createManagementClient`) where available.
|
|
175
|
+
- Programmatic experiment management: `@apex-inc/sdk` management client (`createManagementClient`) where available. Grouped by surface: `apex.experiments.*`, `apex.links.*`, `apex.conversions.*`, `apex.adoption.*`, `apex.communications.*`, `apex.mobile.skan.*`.
|
|
@@ -337,7 +337,7 @@ financial boundary with no API-key lane).
|
|
|
337
337
|
|
|
338
338
|
## Recipe: Set up Apex from this repo
|
|
339
339
|
|
|
340
|
-
If get_setup_state shows first-run incomplete, name the
|
|
340
|
+
If get_setup_state shows first-run incomplete, name the merchant before inventing events.
|
|
341
341
|
|
|
342
342
|
1. Call `get_setup_state`. If the shop has no name, no vertical, or no conversion model, stop.
|
|
343
343
|
2. `update_organization` with the shop's real name. `update_workspace` with `workspace_name`, `vertical`, `conversion_model`, and `website_url` when you know them. The rail finishes from those saves — do not look for a mark-complete tool.
|
|
@@ -346,3 +346,26 @@ If get_setup_state shows first-run incomplete, name the shop before inventing ev
|
|
|
346
346
|
5. Finish writable setup with the matching tools (`claim_links_subdomain`, `set_consent_posture`, webhooks, scoring, invitations, engagement, app platforms, attribution, sender domain, push keys, protect auto-pause). Consent answers are not a live-site tap. Sender-domain records issued are not green — poll `get_sender_domain` until verified.
|
|
347
347
|
6. Hop only when the next incomplete step is a hop: `start_integration_install` for GitHub, Stripe, Meta, Google, LinkedIn, or TikTok. Legal acknowledgements stay on the opened page. You cannot click the ad consoles or the DNS registrar for them.
|
|
348
348
|
7. After ads are connected, make a paid Apex Link before inventing UTMs. Do not stamp conversion ack. `create_apex_link` writes the paid medium. `set_ad_measurement_events` chooses which sales to forward and returns a URL if the legal sentence is still open. `set_conversion_source_of_truth` picks who owns the sale. The rail finishes when those things exist — there is no mark-complete tool.
|
|
349
|
+
|
|
350
|
+
## Recipe: Apex Links for app installs (Mobile Measurement)
|
|
351
|
+
|
|
352
|
+
An Apex Link is a short URL that routes to the App Store / Play Store when the app is not installed and carries the click through to the first open (deferred deep link). Apex credits the install to the link's campaign and sends it back to the ad network the click came from.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { createManagementClient } from "@apex-inc/sdk";
|
|
356
|
+
const apex = createManagementClient({ apiKey, workspaceKey, apiUrl });
|
|
357
|
+
|
|
358
|
+
// One link per campaign. UTMs become the campaign on the Mobile home's table.
|
|
359
|
+
const link = await apex.links.create({
|
|
360
|
+
slug: "spring-bows",
|
|
361
|
+
destinationUrl: "https://shop.example.com/spring",
|
|
362
|
+
utmSource: "meta",
|
|
363
|
+
utmMedium: "paid_social",
|
|
364
|
+
utmCampaign: "spring-bows",
|
|
365
|
+
});
|
|
366
|
+
// Put `https://<your-subdomain>.links.apex.inc/${link.slug}` in the ad.
|
|
367
|
+
|
|
368
|
+
const live = await apex.links.list({ enabled: true });
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Experiments live under `apex.experiments.*` (`list`, `get`, `getResults`, `activate`, `promote`, `assignVariant`, `checkConflicts`, `checkLiveTests`). `apex.mobile` is reserved for install attribution and SKAN values.
|
|
@@ -92,6 +92,25 @@ This is the canonical journey-skill workflow. **Always involve apex-communicatio
|
|
|
92
92
|
6. Walk the user through the apex.trackCart helpers their app needs to call (see apex-growth-tracking for the SDK shape)
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### "Send new installers a first letter" (mobile-install-nudge)
|
|
96
|
+
|
|
97
|
+
The first install letter is a seeded template, not a journey to author from scratch. It starts on `app_install`, waits 24 hours, leaves the moment the person signs up, and sends one letter over push and email with an Apex Link back into the app.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
1. get_setup_state → the Mobile Measurement step "first-letter" says whether the shop can reach installers at all
|
|
101
|
+
(push keys or a verified sender). If neither: stop and send them to Set up Apex. publish_journey refuses
|
|
102
|
+
a letter it cannot deliver; do not work around that.
|
|
103
|
+
2. list_journeys → is a mobile-install-nudge journey already there? Turning it on twice sends two letters.
|
|
104
|
+
3. create_journey_from_template({ template_id: "mobile-install-nudge" }) → a draft with the trigger, wait,
|
|
105
|
+
exit and send already in place. Do not add_journey_step; review with get_journey.
|
|
106
|
+
4. Reach is honest by construction: push goes to the device token, so an installer with no account still gets
|
|
107
|
+
it; email only goes to people the shop already knows. Say that — "everyone who installs gets the push;
|
|
108
|
+
only known people also get the email."
|
|
109
|
+
5. publish_journey (after check_live_tests). Sends bill the communications meter.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The setup step in the dashboard does steps 1–5 in one click with a reach preview; prefer sending the shop there when they are not in the editor.
|
|
113
|
+
|
|
95
114
|
### "Audit my journeys for missing exits"
|
|
96
115
|
|
|
97
116
|
```
|