Developer

Tracking, goals, targeting, preview, and harness

Generate tracking manifests, manage goals, test targeting, open QA previews, and publish AB Test Harness artifacts.

These command groups support measurement setup, live verification, targeting diagnostics, and external harness preview workflows.

Tracking manifests

bash
apex tracking inspect \
  https://example-store.test/products/example \
  https://checkout.example-store.test/thank-you \
  --shop-id 00000000-0000-4000-8000-000000000020 \
  --out ./.apex/tracking-manifest.json
 
apex tracking plan ./.apex/tracking-manifest.json
apex tracking qa ./.apex/tracking-manifest.json
CommandArgumentsFlagsBehavior
apex tracking inspect<urls...>--shop-id <id>, --endpoint <url>, --out <path>Writes a manifest with normalized URLs, domains, recommended cookie domain, snippet, handoff settings, placeholder steps/goals, and QA checks.
apex tracking plan[manifest]None beyond global flagsPrints the implementation plan from a manifest.
apex tracking qa[manifest]None beyond global flagsPrints the QA checklist from a manifest.
apex tracking verifyNone--minutes <n>, --limit <n>, --experiment <id>, --event-type <type>Reads recent live tracking events for the current shop.
apex events exportNone--from <iso>, --to <iso>, --experiment <id>, --event-type <types>, --qa <scope>, --limit <n>, --out <path>, --json-summaryExports every raw event in a window of at most 31 days whose end is not in the future.

apex tracking verify accepts comma-separated event types:

bash
apex tracking verify --minutes 120 --limit 20 --event-type pageview,purchase --json

Human output includes a compact properties preview. Verification is capped and intended for spot checks; use apex events export for complete counts and properties up to 32,768 characters per event (up to 20 event types per export):

bash
apex events export \
  --from 2026-08-01T00:00:00Z \
  --to 2026-08-02T00:00:00Z \
  --event-type pageview,purchase \
  --out events.ndjson

The export follows opaque keyset cursors until the server reports the final page, then compares the number written with the first page's total. Use --qa exclude for non-QA traffic or --qa only for QA traffic; the default include matches raw storage. In JSON mode (--json or piped stdout), --out is required: rows go to the file and stdout carries one summary envelope. In an interactive terminal rows print to stdout unless --out is set; --json-summary asks for the summary envelope there too. Invalid timestamps and windows return invalid_window with invalid_timestamp, to_not_after_from, window_too_long, or to_in_future in the details. A failed run removes the partial --out file so it can be retried; an existing file is refused and is never overwritten.

Purchase-capture verification runbook

Run the read-only battery after publishing purchase tracking:

bash
apex shops doctor --check purchase_capture
apex tracking verify --event-type goal,conversion,purchase,checkout_completed,order_created --minutes 10080 --json
apex install check --shop-id "$APEX_SHOP_ID"

The doctor verdict is deliberately three-state. OK requires a served purchase plane, a mature publication-anchored window, known historical order cadence, and captured revenue events. DARK means a stage is absent or orders are expected while capture is zero. NO_DATA means the window is too young or cadence is unknown; it is never treated as healthy. The duplicate-order warning starts above 2%. Magnitude mismatch uses |revenue - sum(price * quantity)| > max(0.01, 10% of revenue) as an advisory signal because shipping and tax may legitimately differ. Reconciliation excludes pre-publication and consent-suppressed orders.

Control-goal discriminator

When purchase is dark, add temporary diagnostic, read-only observations for sibling GA4 events already emitted by the page (for example begin_checkout and add_payment_info) and compare them with purchase over the same publication-anchored window. Sibling events present while purchase is absent isolates the checkout-to-confirmation boundary; all siblings absent points earlier, at the data-layer or consent boundary. Do not turn diagnostic siblings into business conversion goals, and remove the diagnostic trigger after the boundary is identified.

Generic rescue patterns

  • Session-storage purchase rescue: immediately before a checkout redirect, store the minimum purchase context (stable order ID, revenue, currency, and line items) in sessionStorage. On the confirmed return page, validate and emit it once, then delete it. Namespace the key, set a short expiry, and deduplicate by order ID so reloads cannot double-count.
  • PSP redirect-return rescue: when a payment service skips the normal thank-you page, recognize only its verified success-return route, reconstruct the purchase from trusted return-page context, and re-emit the same canonical purchase payload once. Require a stable transaction ID, expire the rescue record, and never infer success from an unverified query parameter alone.

Both recipes preserve the existing write plane; the CLI commands above only observe and reconcile what that plane produced.

Goals

  • Measurement choice and value-source fields render only for custom goals; existing non-custom value goals keep their stored configuration and remain editable (grandfathered).
bash
apex goals list --limit 50
 
apex goals create \
  --name "Add to cart click" \
  --type click \
  --selector "[data-add-to-cart]" \
  --page product-detail \
  --count-once
 
apex goals update 00000000-0000-4000-8000-000000000030 \
  --event-name purchase_completed \
  --capture '[{"field":"order_id","source":"property","key":"order_id"}]'
CommandFlags
apex goals list--limit <n>
apex goals create--name <name>, --type <type>, --page-id <id>, --page <id-or-key>, --selector <selector>, --text-match <text>, --url-pattern <pattern>, --event-name <name>, --count-once, --capture <json-or-path>
apex goals update<id>, plus the same editable fields as create, with --no-count-once available; pass --page-id null or --page null to clear page scope.

Goal type values are click, pageview, custom, and revenue. Capture can be inline JSON or @path/to/capture.json. Click goals require a page. Run apex pages list to find its ID or key. Pass --page-id <id> for a UUID with no lookup or pages:read permission, or --page <id-or-key> for an ID or a key resolved by lookup (keys need pages:read). Use only one of these options.

Pages from the CLI. apex pages create --help and apex pages update --help list every body key; apex schema --json includes their body schemas. Set match_type to match the pattern: exact (the create default), contains, wildcard, or regex. The CLI refuses wildcard or regex shapes under a contradictory match type with match_type_mismatch; pass --literal to store the patterns as written. An update without a match type leaves it unchanged and skips checks that depend on its stored value.

bash
apex pages create '{"name":"Product pages","url_pattern":"/products/*","match_type":"wildcard","include_rules":["/collections/*/products/*"],"trigger":{"mode":"immediate"}}'

Targeting tests

Targeting schema

An experiment with no targeting reads targeting as null in v1 responses. Any non-null targeting carries both url and authorUrl arrays. apex experiments pull also returns both arrays in targeting.json, using canonical rule objects: { type: "simple" | "regex", pattern: string, include: boolean, matchType?: "exact" | "contains" | "wildcard" | "regex" }. v1 writes accept only this canonical shape; older rows stored in legacy shapes (bare strings, urls, rules) are normalized on read. No migration rewrites stored rows; the compatibility projection runs only on read.

The SDK enforces deviceType (edge falls back to the SDK for this rule); apex targeting test evaluates URL rules only and reports a device restriction as a runtime caveat rather than checking it.

bash
apex targeting test https://example-store.test/products/example --status all
apex targeting test https://example-store.test/products/example --status draft,running --json

apex targeting test evaluates URL targeting for the supplied page and returns matching count, experiment rows, and caveats from the API.

Note: tests that target multiple pages or audiences can now declare a match mode of ANY or ALL (the SDK, edge engine, and web evaluators honor this). When multiple selections are present the setup UI exposes an ANY/ALL choice to require either any matching selection or all selected ones. Existing tests retain the prior any-match behavior.

QA preview URL

bash
apex preview homepage-hero-trust-copy
apex preview homepage-hero-trust-copy variant-a --store-url https://example-store.test/products/example --no-open

apex preview resolves the experiment by ID or slug, defaults to the first non-control variation, and builds a forced QA URL with drip_force, drip_experiment_id, drip_variation_id, drip_qa=1, and drip_debug=1. It opens the browser unless --no-open is present.

Use drip_force=EXP:~ to hold one experiment at Original: it receives no assignment, exposure, stickiness, or page changes. Other tests run normally. Combine it with a pin, for example drip_force=EXP:~,OTHER:VAR, to hold EXP while previewing OTHER. A hold does not survive a cross-domain redirect handover.

Forced preview isolation and coexistence

The QA panel’s Coexist with live tests toggle sets the same drip_force_coexist=1 choice for the session. Pre-launch QA and ready-to-launch experiments appear in the panel only when the server verifies the QA session’s authorization for this shop. Three authorizations count: a signed QA link opened from the dashboard (a client link without debug does not list them), an active harness session for the shop, or a preview-read capability scoped to the shop. These experiments render only when pinned. drip_qa=1 alone lists running tests.

A variation pin in drip_force (or legacy experiment/variation pair) isolates the SDK preview to the forced experiments and any experiment handles listed in drip_exp. Other experiments, personalizations, and flags are suppressed and produce no exposure or tracking. This also applies to cookie-persisted and QA assistant forces. The assistant's activation diagnostics show suppressed_by_forced_preview.

Add drip_force_coexist=1 to restore coexistence for the QA session. The SDK stores this choice in the drip_force_coexist cookie alongside drip_qa_force, renewing both for 30 minutes on QA force persistence. Navigation preserves the choice; drip_force_coexist=0 disables it, and exiting QA clears it. The runtime snapshot exposes forcedPreviewCoexist.

Without a force, running experiments targeting the page can be assigned unless an exclusion group separates them. Assignments apply in served config order. When multiple experiments claim the same mutation selector in QA mode, the QA bundle’s debug output warns Shared container <selector>: <experiment>/<variation> and <experiment>/<variation>; later wins: the later assignment wins on overlapping mutations. This diagnostic includes selected URL-block mutations and does not change rendering. The QA assistant runtime snapshot exposes the same selector and experiment/variation pairs in conflicts. The conflict warning and snapshot conflicts list are available only in the QA bundle, not in the live bundle.

AB Test Harness

bash
apex harness init ./ab-test-harness/experiments/homepage-hero
apex harness dev ./ab-test-harness/experiments/homepage-hero --no-open
apex harness publish ./ab-test-harness/experiments/homepage-hero
CommandArgumentsFlagsBehavior
apex harness init<folder>--forceWrites apex.harness.json from existing harness metadata, unless it already exists.
apex harness dev<folder>--no-openRuns ab-harness build, uploads the artifact to an Apex harness session, opens or prints the preview URL, and watches for rebuilds.
apex harness publish<folder>None beyond global flagsRuns ab-harness verify, builds, uploads, and publishes the latest harness artifact to the Apex draft variation.

Set AB_HARNESS_BIN when the harness executable is not named ab-harness on PATH.

Harness dev uses experimentName and targetUrl only when it creates the experiment. Selectors and the cleanup reset script are sent only when the harness config sets them.

apex preview --proxy --json returns the existing session_id, preview_url, and expires_at plus qa_run: { id, created, refreshed, automated_evidence_count }. The receipt names the QA run created or refreshed by recording this preview. automated_evidence_count counts the normalized evidenceRecords across all evidence kinds, matching the list shown by apex experiments qa status. Human output prints QA run <id> created or QA run <id> refreshed (N automated evidence records).

Decision latency before launch

bash
apex tracking latency MOM-027 --days 7
apex tracking latency MOM-027 --days 7 --qa-session <session-id> --json

Compare forced-preview and natural loads before launching an above-the-fold change. --days accepts 1–30 (default 7). The table shows samples, decision p50/p95/p99, config-fetch p50/p95 in milliseconds, and inline/cache/network/none counts. --json includes the API body in the standard CLI envelope, including assignment-ready percentiles and position: "runtime_inline_no_change".

All buckets include only page-load events (perf_event_kind = 'init' and exclude route-change events and bot traffic. Forced rows contain the experiment's resolved ID in forced_experiment_ids. Natural rows are shop-wide loads with no forced IDs and qa_mode = 0. The optional QA-session bucket covers that session; it can overlap forced rows. Fewer than five measured samples produce null percentiles (- in the table). An unavailable analytics read returns an error, never a zero-sample success.

For page-readable timestamps during forced previews, see SDK decision timing. The runtime remains unchanged. Natural runtime decides within tens of milliseconds of SDK start when config ships inline with the snippet (measured p50 12 ms / p95 54 ms on a large storefront). QA previews, screenshot-mode previews and cookie-forced sessions take the QA config lane and fetch fresh config over the network (measured p50 1.3 s / p95 7 s); they are not representative of launch behaviour. URL-forced sessions without a qa parameter keep the inline or cached config, so these measurements do not describe them. No config preload or anti-flicker hold is planned.

Tracking verification limits

apex tracking verify reads at most 200 of the newest events from a window of at most 7 days. When the limit is reached, truncated is true and totals describe that sample only. The response includes the exact from, to, minutes, and limit window.