Developer

Public API surface map

Routes in the Apex public API v1 contract, grouped by host and authentication.

Public API v1

The public Apex API is the /api/v1/* surface defined by the served OpenAPI contract. Contract routes require an API key unless listed under "Routes without API-key authentication". The same document also describes the explicitly listed dashboard session operations. Use /api/v1/openapi.json for methods, request bodies, responses, and schemas.

Dashboard handlers outside /api/v1/* are session-scoped application internals. Only those registered under "Dashboard session routes" appear in this map. Separately, GET /api/exp is the SDK's storefront-configuration transport. It takes a shopId, needs no session or API key, and is served with a public 30-second cache. It is an SDK delivery endpoint, not part of the v1 contract; integrate through the hosted SDK script rather than calling it directly.

The route inventory below is verified against src/lib/api/openapi-spec.ts; its drift test fails if the OpenAPI paths, their operation-level servers, their security declarations, or this list change independently. OpenAPI parameter syntax ({id}) is used verbatim. Worker operations declare the events host as their server and are listed with the full URL. Dashboard session operations declare / as their server and use absolute /api/* paths.

Contract routes

  • /api/v1/account/profile
  • /api/v1/admin/client-links
  • /api/v1/admin/customers
  • /api/v1/admin/customers/{orgId}
  • /api/v1/admin/customers/{orgId}/move-stores
  • /api/v1/admin/customers/{orgId}/setup
  • /api/v1/admin/customers/{orgId}/unique-visitors
  • /api/v1/admin/settings
  • /api/v1/admin/shops/{id}/archive
  • /api/v1/admin/shops/{id}/restore
  • /api/v1/admin/shops/{id}/settings
  • /api/v1/admin/shops/{id}/tracking-health
  • /api/v1/admin/tracking/quarantine
  • /api/v1/analytics/entry-exit
  • /api/v1/analytics/geo
  • /api/v1/analytics/order-journey
  • /api/v1/analytics/pages
  • /api/v1/analytics/profit
  • /api/v1/analytics/realtime
  • /api/v1/analytics/sessions
  • /api/v1/analytics/stats
  • /api/v1/analytics/suggest-goals
  • /api/v1/analytics/timeseries
  • /api/v1/analytics/traffic
  • /api/v1/analytics/url-inventory
  • /api/v1/analytics/visitor-series
  • /api/v1/assets
  • /api/v1/assets/{id}
  • /api/v1/assets/{id}/restore
  • /api/v1/audit-logs
  • /api/v1/backlog
  • /api/v1/backlog/fields
  • /api/v1/backlog/fields/{id}
  • /api/v1/backlog/{id}
  • /api/v1/backlog/{id}/approve
  • /api/v1/backlog/{id}/archive
  • /api/v1/backlog/{id}/design
  • /api/v1/backlog/{id}/reject
  • /api/v1/backlog/{id}/restore
  • /api/v1/brand-context
  • /api/v1/commerce/catalog
  • /api/v1/consent
  • /api/v1/cost-settings
  • /api/v1/data-deletion
  • /api/v1/data-export
  • /api/v1/environments
  • /api/v1/events/export
  • /api/v1/exclusion-groups
  • /api/v1/exclusion-groups/{id}
  • /api/v1/exclusion-groups/{id}/members
  • /api/v1/exclusion-groups/{id}/restore
  • /api/v1/experiment-events
  • /api/v1/experiments
  • /api/v1/experiments/by-slug/{slug}
  • /api/v1/experiments/conflicts
  • /api/v1/experiments/filesystem/schema
  • /api/v1/experiments/generate
  • /api/v1/experiments/{id}
  • /api/v1/experiments/{id}/commerce
  • /api/v1/experiments/{id}/commerce/price/csv
  • /api/v1/experiments/{id}/commerce/price/rows
  • /api/v1/experiments/{id}/complete
  • /api/v1/experiments/{id}/duplicate
  • /api/v1/experiments/{id}/filesystem
  • /api/v1/experiments/{id}/filesystem/apply
  • /api/v1/experiments/{id}/filesystem/plan
  • /api/v1/experiments/{id}/idea
  • /api/v1/experiments/{id}/pause
  • /api/v1/experiments/{id}/qa
  • /api/v1/experiments/{id}/qa/commerce-probes
  • /api/v1/experiments/{id}/qa/events
  • /api/v1/experiments/{id}/qa/evidence
  • /api/v1/experiments/{id}/qa/run
  • /api/v1/experiments/{id}/readiness
  • /api/v1/experiments/{id}/results
  • /api/v1/experiments/{id}/results/ad-roas
  • /api/v1/experiments/{id}/results/by-channel
  • /api/v1/experiments/{id}/results/explain
  • /api/v1/experiments/{id}/screenshots
  • /api/v1/experiments/{id}/start
  • /api/v1/experiments/{id}/variations/generate
  • /api/v1/experiments/{id}/variations/{variationId}/build
  • /api/v1/experiments/{id}/variations/{variationId}/files
  • /api/v1/experiments/{id}/variations/{variationId}/files/{path}
  • /api/v1/experiments/{id}/variations/{variationId}/revisions
  • /api/v1/experiments/{id}/variations/{variationId}/workspace
  • /api/v1/external-pixels/snippet
  • /api/v1/external-tracking
  • /api/v1/flags
  • /api/v1/flags/{id}
  • /api/v1/flags/{id}/activate
  • /api/v1/flags/{id}/archive
  • /api/v1/flags/{id}/code
  • /api/v1/flags/{id}/conclude
  • /api/v1/flags/{id}/experiences
  • /api/v1/flags/{id}/experiences/{experienceId}
  • /api/v1/flags/{id}/pause
  • /api/v1/flags/{id}/qa-devices
  • /api/v1/flags/{id}/qa-devices/{identity}
  • /api/v1/flags/{id}/reopen
  • /api/v1/flags/{id}/rules
  • /api/v1/flags/{id}/rules/reorder
  • /api/v1/flags/{id}/status
  • /api/v1/forms
  • /api/v1/forms/detect
  • /api/v1/forms/{key}
  • /api/v1/forms/{key}/errors
  • /api/v1/forms/{key}/fields
  • /api/v1/forms/{key}/fields/{identity}
  • /api/v1/forms/{key}/sessions
  • /api/v1/forms/{key}/summary
  • /api/v1/funnels
  • /api/v1/funnels/{id}
  • /api/v1/funnels/{id}/restore
  • /api/v1/funnels/{id}/run
  • /api/v1/goals
  • /api/v1/goals/{id}
  • /api/v1/goals/{id}/restore
  • /api/v1/harness/sessions
  • /api/v1/harness/sessions/{id}/publish
  • /api/v1/harness/sessions/{id}/versions
  • /api/v1/inbox/messages
  • /api/v1/inbox/read-state
  • /api/v1/inspect
  • /api/v1/install-domains
  • /api/v1/install-status
  • /api/v1/install/assess
  • /api/v1/key
  • /api/v1/keys
  • /api/v1/keys/self/shop
  • /api/v1/keys/{id}
  • /api/v1/landing-pages
  • /api/v1/landing-pages/domains
  • /api/v1/landing-pages/domains/{id}
  • /api/v1/landing-pages/domains/{id}/verify
  • /api/v1/landing-pages/{id}
  • /api/v1/landing-pages/{id}/deploy
  • /api/v1/landing-pages/{id}/experiment
  • /api/v1/landing-pages/{id}/filesystem
  • /api/v1/landing-pages/{id}/filesystem/apply
  • /api/v1/landing-pages/{id}/filesystem/plan
  • /api/v1/landing-pages/{id}/filesystem/upload
  • /api/v1/landing-pages/{id}/filesystem/upload-grant
  • /api/v1/landing-pages/{id}/preview
  • /api/v1/landing-pages/{id}/rollback
  • /api/v1/landing-pages/{id}/versions
  • /api/v1/me
  • /api/v1/me/context
  • /api/v1/notifications/ready/email
  • /api/v1/notifications/ready/slack
  • /api/v1/org
  • /api/v1/org/invitations
  • /api/v1/org/invitations/{id}
  • /api/v1/org/members
  • /api/v1/org/members/{id}
  • /api/v1/pages
  • /api/v1/pages/{id}
  • /api/v1/personalizations
  • /api/v1/personalizations/{id}
  • /api/v1/personalizations/{id}/activate
  • /api/v1/personalizations/{id}/archive
  • /api/v1/personalizations/{id}/pause
  • /api/v1/preview-sessions
  • /api/v1/roadmap/approved
  • /api/v1/roadmap/items/{id}/handoff
  • /api/v1/runtime-settings
  • /api/v1/runtime-settings/recipes
  • /api/v1/screenshots/{jobId}
  • /api/v1/segments
  • /api/v1/segments/{id}
  • /api/v1/shops
  • /api/v1/shops/{id}
  • /api/v1/shops/{id}/audit-logs
  • /api/v1/shops/{id}/auto-stop
  • /api/v1/shops/{id}/engine
  • /api/v1/shops/{id}/engine/guardrails
  • /api/v1/shops/{id}/engine/interventions/{iid}/rollout
  • /api/v1/shops/{id}/engine/pause
  • /api/v1/shops/{id}/engine/proposals/{pid}/approve
  • /api/v1/shops/{id}/engine/proposals/{pid}/reject
  • /api/v1/shops/{id}/engine/proposals/{pid}/veto
  • /api/v1/shops/{id}/engine/start
  • /api/v1/shops/{id}/engine/stop
  • /api/v1/shops/{id}/guardian
  • /api/v1/shops/{id}/install-instructions
  • /api/v1/shops/{id}/plugin-token
  • /api/v1/shops/{id}/shopware/prebind
  • /api/v1/shops/{id}/store-profile
  • /api/v1/shops/{id}/store-profile/facts/{factId}
  • /api/v1/shops/{id}/store-profile/findings/review
  • /api/v1/shops/{id}/store-profile/learning-runs
  • /api/v1/shops/{id}/testing-defaults
  • /api/v1/signals
  • /api/v1/signals/{id}
  • /api/v1/sos
  • /api/v1/sos/attachment-uploads
  • /api/v1/sos/mine
  • /api/v1/surveys
  • /api/v1/surveys/{id}
  • /api/v1/surveys/{id}/complete
  • /api/v1/surveys/{id}/duplicate
  • /api/v1/surveys/{id}/pause
  • /api/v1/surveys/{id}/start
  • /api/v1/targeting/test
  • /api/v1/team/invitations
  • /api/v1/team/invitations/{id}
  • /api/v1/team/members
  • /api/v1/team/members/{id}
  • /api/v1/telemetry/cli
  • /api/v1/test-results
  • /api/v1/test-results/import/confirm
  • /api/v1/test-results/import/inspect
  • /api/v1/test-results/import/preview
  • /api/v1/test-results/{id}
  • /api/v1/tracking/discovery
  • /api/v1/tracking/health
  • /api/v1/tracking/latency Compare forced-preview and natural page-load decision latency.
  • /api/v1/tracking/simulate-trigger
  • /api/v1/tracking/verify Look up one test phone or account with identity=<device or user id> (exact visitor_id or user_id, 1 to 128 characters after trimming). Use the device UUID before identify and the app user id after identify. With identity, minutes defaults to 60 and is capped at 1,440; without it, the default remains 120 and the maximum is 10,080. Events include platform, app version, SDK version and environment. Requires a private API key with experiments:read permission.
  • /api/v1/usage/unique-visitors
  • /api/v1/variations
  • /api/v1/variations/{id}
  • /api/v1/variations/{id}/restore
  • /api/v1/webhooks
  • /api/v1/webhooks/{id}
  • /api/v1/webhooks/{id}/deliveries
  • /api/v1/webhooks/{id}/restore
  • /api/v1/webhooks/{id}/test

Organization member suspension

Owners and admins can use PATCH /api/v1/org/members/{id} with { "suspended": true, "reason": "Offboarded" } to suspend a member, or { "suspended": false } to reinstate them. The optional reason must contain non-whitespace text and is only allowed when suspending. Send role or default_shop_role changes in a separate request; combining them with suspended returns 400.

The member list and PATCH response include nullable suspended_at, suspended_by, and suspended_reason fields. Repeating the current state returns 200 without adding another audit entry. Self-suspension and self-reinstatement return 400 with cannot_suspend_self; suspending the last active owner returns 400 with last_owner. Missing members return 404 with not_found. Admins cannot suspend or reinstate owners; both requests return 403.

A suspended member's user key receives 403 with member_suspended when accessing that organization. Account context discovery excludes suspended organizations. Access to other active memberships remains available, and reinstatement restores organization access. CLI login approval refuses an account key when every organization membership is suspended. Shop API keys carry no user identity and are not affected by member suspension.

Dashboard session routes

These routes return 404 while the server rollout is off. Brand undo uses explicit all_with_source, copies-only all, or one shop_id, with per-store atomic receipts and access redaction.

These POST operations require a dashboard session cookie and an explicit x-apex-shop header; they do not accept API keys. Both require experiments:write, and restoring a running test also requires experiments:launch and the ordinary resume safety checks, including QA. Preview is read-only. Apply binds the reviewed completion and preview version, requires an idempotency key and a reason, and restores the same run and assignments. A committed refresh failure returns HTTP 200 with receipt warnings; retry the same key to repair publication. Already-sent external effects cannot be recalled, and queued retries or in-flight sends may still deliver.

  • /api/experiments/{id}/undo-completion
  • /api/experiments/{id}/undo-completion/preview
  • /api/flags/{id}/experiences
  • /api/flags/{id}/experiences/{experienceId}
  • /api/flags/{id}/rules
  • /api/flags/{id}/rules/reorder
  • /api/multi-shop-programs/{id}/actions/undo-completion
  • /api/multi-shop-programs/{id}/actions/undo-completion/preview

Worker runtime routes

Served by the events Worker; the OpenAPI operations declare https://events.drip-apex.com/v1 as their server. Neither route exists on the dashboard host.

  • https://events.drip-apex.com/v1/assign
  • https://events.drip-apex.com/v1/handover/verify

Routes without API-key authentication

These operations declare security: [] in the contract. Handover verification requires a valid X-Drip-Shop header and a signed, unexpired verifyToken in the JSON body. The token must match the resolved shop. A missing or invalid shop header returns 400 before token verification. An invalid, expired, or mismatched token returns 401. No ingest API key is required.

  • https://events.drip-apex.com/v1/handover/verify

Note: This release adds the /api/v1/analytics/order-journey analytics contract for bounded order-journey diagnostics and Shopify order correlation; see docs/specs/spec-6686-order-journey.md for the contract details.

Results for an audience and written summaries

Call GET /api/v1/experiments/{id}/results?segment=device:mobile for mobile or ?segment=device:desktop for desktop. Omit both slice parameters for All. exclude_segment=dim:value reads the complement; segment wins if both are present. Dimensions are device, country, traffic_source, platform, campaign, visitor_kind, landing_page and weekday. Values match observed stored values exactly. currency remains an optional three-letter ISO code.

Every response includes segment_applied: { requested: { dim, value, mode } | null, applied, reason }. Invalid or unknown requests, unobserved values and unavailable preflights return the unsliced read with applied: false and respectively invalid_value, unknown_dimension, unobserved_value or preflight_unavailable. Check this field before labelling numbers as mobile or desktop.

Store-order commerce is unsliced. For an applied segment it becomes { unavailable: true, reason: "not_sliced_by_segment" } without store money or orders. Tracked revenue stays sliced. Per-segment ARPU is directional: revenue.perTreatment[].result.significance carries reason: "revenue_significance_uncertified" and unavailableContext: "view"; there is no certified per-device revenue confidence. The primary-goal probability is decision.judgedConfidence and measures that goal, not ARPU.

Use these fields for a dashboard card, pairing each treatment with control:

Card numberField or calculation
Usersarms[].visitors
TransactionsRevenue goal metric controlDisplayOrders / variantDisplayOrders; check their source fields (attributed or pixel). controlCount / variantCount are converting visitors, not orders.
CRDisplay orders / arm visitors
AOVRevenue goal metric controlAov / aov (revenue / display orders)
ARPUarms[].revenue / arms[].visitors
Each deltaTreatment / control − 1; undefined when control is zero
Runtime revenue delta(Treatment ARPU − control ARPU) × treatment visitors

Arm revenue includes matched orders without a tracked visit while arm visitors exclude those visits. The metric's controlValuePerVisitor / valuePerVisitor use its goal population, so they can differ from card ARPU. Keep these definitions distinct. Commerce-system order truth is available only on the unsliced call.

The unsliced response also includes narrative, either null or { learning, psychology, insights: [{ title, description }], follow_ups, stakeholder_summary, generated_at }. generated_at is the cached row's ISO creation time. Only a summary matching the current v15 input hash is returned. When narrative_unavailable_reason is generating, generation runs after the numbers response; a later All call returns the cached summary. Other reasons are gated (the conversion significance floor), not_narratable (no readable narrative dataset) and request_unsliced (a slice was requested). Sliced requests never look up or generate a summary. Count-every goals in summaries report events per visitor, not a percentage conversion rate. Concurrent cold All calls can each generate for the same hash; the existing unique-key upsert retains one row.

CLI: apex experiments results <id-or-slug> --segment device:mobile or --exclude-segment device:mobile. MCP get_results accepts segment and exclude_segment with the same values and reports whether the slice applied.

Structured test-result imports

  • GET /api/v1/test-results lists past results for the selected shop with source=import|native|all, limit (1–200, default 50), and cursor pagination in { data, pagination }.
  • GET /api/v1/test-results/{id} reads a result by UUID, legacy ID, or test key in { data }; missing or foreign-shop results return 404. Both reads accept shop or user keys with experiments:read. Each row carries result_source (import or native), classified like the Program page: a row with a source ID or source name is imported, including results migrated from the backlog.
  • DELETE /api/v1/test-results/{id} permanently removes one imported result with experiments:write and JSON { "confirm": true }, returning the removed row in { data }. The full record is retained in the audit log. Native results return 409, absent or foreign-shop results return 404, missing confirmation returns 400, and an unavailable required audit returns 500 without removing anything.
  • POST /api/v1/test-results/import/inspect accepts multipart files and returns detected columns and values.
  • POST /api/v1/test-results/import/preview accepts the same files plus a JSON mapping part and returns mapped rows, row errors, and warnings without writing.
  • POST /api/v1/test-results/import/confirm accepts JSON { rows } in batches of at most 500 and writes imported test results.

All three routes require experiments:write; user API keys must also pin a shop context.

Rollout code edits

PATCH /api/v1/flags/{id}/code requires flags:write. Send custom_js and/or custom_css (null clears; omission preserves), plus expected_updated_at from the On variant in GET /flags/{id}, or expected_artifact_hash from a prior code-save receipt. Stale versions return 409. Only On code and its version are changed; targeting, schedule, rollout, status, flag identity and Off stay intact. Edited code replaces that side's external artifact reference, if present.

The response reports saved, artifact_hash, publication, reviewStatus, blockingIssues and warningIssues (inside data on success). Blocking issues refuse persistence. A 502 with saved: true means the save committed but shop publication failed; read back before retrying. Pending/warning publication statuses remain distinct from persistence. Render evidence is not accepted by this endpoint; needs_review is advisory for an already active rollout.

CLI: apex flags update-code <id> --js ./on.js [--css ./on.css]. The CLI reads the current On version before saving (requiring flags:read as well) and never automatically retries a conflict.