Generated OpenAPI reference
API for managing A/B testing experiments, variations, goals, webhooks, and screenshots. Every v1 response carries X-Apex-Cli-Minimum and X-Apex-Cli-Latest version-policy headers. Write requests from an Apex CLI below the enforced minimum answer 426; read requests keep working. Landing-page bundles allow 300 files and 100 MB per version, including MP4/WebM up to 50 MB per video (3 MB per other file). Filesystem/upload-grant issues a 15-minute, version-bound grant for direct Worker multipart upload above 3 MB.
Assignments
Get deterministic server-side assignments
/assign
Parameters
shop_idin query requireduser_idin query requiredurlin query - Browser URL for storefront URL targeting. App clients may send an app screen URL (app://<bundle id>/<screen>); app callers (publishable-key lane or any app:// URL) are never assigned experiments with storefront URL targeting.environmentin queryexperimentsin query
Responses
200Assignments and program cohort returned400Invalid request, including a customer identifier supplied in the query string instead of a POST body.401429503
curl \
https://app.drip-apex.com/api/v1/assign \
-H "Authorization: Bearer <apex-secret-key>"Get deterministic assignments with commerce proof inputs
/assign
Request body
{
shop_id: string
user_id?: string
logged_in_customer_id?: string
customer_id?: string
decision_use?: "aggregate" | "individual"
url?: string (uri)
environment?: string
experiments?: string[]
cart_token?: string
persist_function_handoff?: boolean
exclusion_group_selections?: object[]
}Responses
200Assignments, program cohort, and commerce authorization returned400401403A logged-in customer assignment was requested for an individual-level decision.429503
curl -X POST \
https://app.drip-apex.com/api/v1/assign \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "example_shop_id"
}'Runtime
Verify a signed cross-domain bucketing handover token
/handover/verify
Worker runtime endpoint. Verifies the token signature, expiry, and shop binding without requiring ingest credentials.
Parameters
X-Drip-Shopin header required
Request body
{
verifyToken: string
}Responses
200Verified handover payload400Missing or invalid shop identifier401Unsigned, forged, expired, or cross-shop handover token500Handover verification is unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/handover/verify \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"verifyToken": "example_verifyToken"
}'Other
Inspect experiment conflicts
/experiments/conflicts
Request body
{
selectors: string[]
exclude_experiment_id?: string (uuid)
}Responses
200Conflict analysis400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/conflicts \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"selectors": [],
"exclude_experiment_id": "uuid-here"
}'Update experiment QA
/experiments/{id}/qa
Approves QA and marks ready to launch, without launching or changing weights. Zero-traffic acknowledgement requires an Admin or Internal person principal; shop API keys cannot acknowledge.
Parameters
scopein query - Set to store to act only on this store; omitted acts on every store when this is a brand-test parent.
Request body
{
acknowledge_degenerate_weights?: boolean
acknowledge_degenerate_weights_hash?: string
scope?: "store"
}Responses
200QA updated: the raw experiment row, plus program on a brand-test parent (#9255)400401403404409QA conflict, including a missing, stale, or unauthorized zero-traffic acknowledgement429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"acknowledge_degenerate_weights": false,
"acknowledge_degenerate_weights_hash": "example_acknowledge_degenerate_weights_hash",
"scope": "store"
}'Reset experiment QA
/experiments/{id}/qa
Responses
200QA reset400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa \
-H "Authorization: Bearer <apex-secret-key>"Read saved commerce QA probe evidence
/experiments/{id}/qa/commerce-probes
Reads saved price, shipping, and post-purchase probe evidence without calling Shopify. Requires experiments:read. Current shipping evidence also returns its saved result, preserving summary, details and optional action: { label: string, href: string }. Results from earlier draft versions are omitted.
Responses
200Saved commerce probe evidence401403404429503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/commerce-probes \
-H "Authorization: Bearer <apex-secret-key>"Run commerce QA probes
/experiments/{id}/qa/commerce-probes
The shipping probe checks each enforced arm against the store rate in every delivery zone and also runs for enforced arms without a threshold. Runs the requested saved-draft commerce probes. Requires experiments:write. When shipping evidence cannot be read, kinds.shipping.result includes a plain-language summary and the same sentence first in details. A missing Shopify permission may also include optional action: { label: "Approve in Shopify", href: string }, linking to Pricing & Offers authorization.
Request body
{
kinds?: ("price" | "shipping" | "post_purchase")[]
}Responses
200Commerce probe results and saved evidence400401403404409Experiment must be draft or paused, or installation unavailable422Probe not applicable or invalid commerce configuration429502Shopify upstream request failed503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/commerce-probes \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"kinds": []
}'Read QA-tagged events
/experiments/{id}/qa/events
Returns goal and custom events for a QA session. Requires experiments:read.
Responses
200QA event groups400401403404429503QA event readback unavailable
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/events \
-H "Authorization: Bearer <apex-secret-key>"Read QA run
/experiments/{id}/qa/run
Creates a run only when none exists. Existing runs are computed in memory without writes: updatedAt is the stored mutation time; computedAt is the ISO timestamp of this read.
Responses
200QA run, including QA destination URLs for goals on pages outside experiment targeting (goal-page entries in urls, and goalPageDestinations evidence on the qa_url_coverage checklist item)400401403404429503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/run \
-H "Authorization: Bearer <apex-secret-key>"Update QA run
/experiments/{id}/qa/run
Request body
{
runId: string
itemId?: string
status?: "pending" | "pass" | "warning" | "fail" | "accepted" | "skipped"
notes?: string
evidence?: object
runNotes?: string
acceptanceCriteriaSummary?: string | null
}Responses
200QA run updated400401403404429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/run \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"runId": "example_runId"
}'Add a custom QA checklist item
/experiments/{id}/qa/run
Request body
{
runId: string
title: string
description?: string
category?: string
required?: boolean
}Responses
201QA checklist item created400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/run \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"runId": "example_runId",
"title": "example_title"
}'Delete custom QA checklist item
/experiments/{id}/qa/run
Parameters
runIdin queryitemIdin query
Request body
{
runId?: string
itemId?: string
}Responses
200QA run deleted400401403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/run \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"runId": "example_runId",
"itemId": "example_itemId"
}'Retired experiment launch endpoint
/experiments/{id}/start
This endpoint is gone. Launch traffic through the dashboard approval workflow.
Responses
410Launch is dashboard-only
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/start \
-H "Authorization: Bearer <apex-secret-key>"Create harness session
/harness/sessions
Request body
{
experimentId?: string
variationId?: string
experimentSlug: string
experimentName: string
variationSlug: string
variationName: string
targetUrl: string (uri)
resetJs?: string | null
antiFlickerSelectors?: string[]
ttlSeconds?: integer
}Responses
201Harness session created400Invalid request or artifact review blocked the session; nothing was created or changed401403404409Artifact conflict during the save, or experiment is not in draft status429503
curl -X POST \
https://app.drip-apex.com/api/v1/harness/sessions \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"experimentSlug": "example_experimentSlug",
"experimentName": "example_experimentName",
"variationSlug": "example_variationSlug",
"variationName": "example_variationName",
"targetUrl": "https://example.com"
}'Create harness session version
/harness/sessions/{id}/versions
Responses
201Version created400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/harness/sessions/exp_123/versions \
-H "Authorization: Bearer <apex-secret-key>"Read platform operator settings
/admin/settings
admin:read plus platform health-section access. Missing undo policy defaults to 7; malformed or unreadable policy returns 503 policy_unavailable.
Responses
200Typed settings and policy version503policy_unavailable
curl \
https://app.drip-apex.com/api/v1/admin/settings \
-H "Authorization: Bearer <apex-secret-key>"Write audited platform operator settings
/admin/settings
admin:write plus platform health-section access. Numeric policy writes require expected_version and reason. All supplied settings and audits commit atomically; boolean-only requests keep their existing contract.
Request body
{
guardian_enabled?: boolean
srm_check_enabled?: boolean
experiment_undo_completion_window_days?: integer
free_plan_days?: any
expected_version?: string
reason?: string
}Responses
200Updated typed settings400Invalid allowlisted setting, version or reason403Insufficient admin or section access409policy_changed; no settings written503policy_unavailable
curl -X PATCH \
https://app.drip-apex.com/api/v1/admin/settings \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'List backlog
/backlog
Parameters
limitin querycursorin queryinclude_archivedin query - Include archived backlog items when true.
Responses
200Paginated backlog400401403429503
curl \
https://app.drip-apex.com/api/v1/backlog \
-H "Authorization: Bearer <apex-secret-key>"Create a backlog item
/backlog
Creates a backlog item. Score and score-provenance fields require an active apex-editor or apex-platform-admin grant on a user API key; shop keys and other users receive 403.
Request body
{ $ref: "BacklogCreate" }Responses
201Created400401403409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/backlog \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'List the shop's idea fields
/backlog/fields
The field definitions whose values ride on each idea's custom_fields.
Parameters
include_archivedin query - Include archived fields when true.
Responses
200Field definitions, in sheet order401403429503
curl \
https://app.drip-apex.com/api/v1/backlog/fields \
-H "Authorization: Bearer <apex-secret-key>"Add an idea field
/backlog/fields
Request body
{ $ref: "BacklogFieldCreate" }Responses
201Created400401403409429503
curl -X POST \
https://app.drip-apex.com/api/v1/backlog/fields \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Reorder the idea fields
/backlog/fields
Places the live fields. Every named field must be live, and the resulting positions must be unique across the whole live sheet, so send the sheet rather than one row when two fields swap places. The body is the bare array or the same array under an order key.
Request body
anyResponses
200Field definitions, in the new order400401403404429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/backlog/fields \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Update an idea field
/backlog/fields/{id}
Request body
{ $ref: "BacklogFieldUpdate" }Responses
200Updated400401403404429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/backlog/fields/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive an idea field
/backlog/fields/{id}
Archiving keeps the values already stored on ideas. The field disappears from reads and rejects new writes.
Responses
200Archived401403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/backlog/fields/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Get a backlogitem
/backlog/{id}
Responses
200Resource401403404429503
curl \
https://app.drip-apex.com/api/v1/backlog/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update a backlogitem
/backlog/{id}
Updates a backlog item. Score and score-provenance fields, including research_hub_round_trip, require an active apex-editor or apex-platform-admin grant on a user API key; shop keys and other users receive 403. Ordinary metadata replacement remains available when neither stored nor incoming metadata contains protected score or prediction provenance. Omit metadata for ordinary edits on scored items. Supplying protected metadata, including unchanged values, or removing stored provenance returns 403. A concurrent metadata change returns 409 with error.code backlog_item_cas_miss, even when expected_version is omitted. Reload the item and retry.
Request body
{ $ref: "BacklogUpdate" }Responses
200Updated400401403404409The supplied version is stale or metadata changed during a non-staff replacement. Reload the item and retry.422429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/backlog/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive a backlog item
/backlog/{id}
Archives the backlog item instead of destroying it.
Responses
200Deleted401403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/backlog/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Get an idea's design board
/backlog/{id}/design
Mockups, links, inspiration, and internal build notes, plus every Research Hub mirror the item carries. The internal half (build notes, inspiration, and links whose switch is off) is redacted for a client viewer: a client shop role in the dashboard, and a user API key whose holder holds one. A secret API key is minted with apikeys:manage, which no client role holds, so it reads the whole board.
Responses
200Design board401403404429503
curl \
https://app.drip-apex.com/api/v1/backlog/exp_123/design \
-H "Authorization: Bearer <apex-secret-key>"Update an idea's design board
/backlog/{id}/design
Replaces the keys it carries and leaves the rest alone. Arrays replace wholesale.
Request body
{ $ref: "BacklogItemDesignPatch" }Responses
200Updated design board400401403404409The idea changed while saving429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/backlog/exp_123/design \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive a backlog item and reject its roadmap decision
/backlog/{id}/archive
Request body
{
feedback?: string | null
reason?: string | null
}Responses
200Archived backlog item401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/backlog/exp_123/archive \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"feedback": "example_feedback",
"reason": "example_reason"
}'Approve a backlog idea into a month
/backlog/{id}/approve
Schedules the idea and records who decided. On a shop where DRIP decides the backlog, a client principal is refused with 403.
Request body
{
roadmap_month: string
comment?: string | null
}Responses
200The idea's decision after approving400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/backlog/exp_123/approve \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"roadmap_month": "example_roadmap_month",
"comment": "example_comment"
}'Reject a backlog idea with a reason
/backlog/{id}/reject
Records the rejection and its reason. The idea stays in the backlog, shown as rejected, rather than being archived.
Request body
{
reason_category: "not_right_for_brand" | "already_tested" | "too_much_effort" | "other"
reason: string
}Responses
200The idea's decision after rejecting400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/backlog/exp_123/reject \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"reason_category": "not_right_for_brand",
"reason": "example_reason"
}'Restore an archived backlog item and reset its roadmap decision to proposed
/backlog/{id}/restore
Responses
200Restored backlog item401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/backlog/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"List personalizations
/personalizations
Parameters
limitin querycursorin query
Responses
200Personalizations with cursor pagination400401403404429503
curl \
https://app.drip-apex.com/api/v1/personalizations \
-H "Authorization: Bearer <apex-secret-key>"Create personalization
/personalizations
Request body
{ $ref: "PersonalizationInput" }Responses
201Created personalization400401403404422Checkout messages work only on A/B tests.429503
curl -X POST \
https://app.drip-apex.com/api/v1/personalizations \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get personalization
/personalizations/{id}
Responses
200Personalization401403404429503
curl \
https://app.drip-apex.com/api/v1/personalizations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update personalization
/personalizations/{id}
Request body
{ $ref: "PersonalizationUpdateInput" }Responses
200Updated personalization400401403404429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/personalizations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Activate personalization
/personalizations/{id}/activate
Responses
200Updated personalization401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/personalizations/exp_123/activate \
-H "Authorization: Bearer <apex-secret-key>"Pause personalization
/personalizations/{id}/pause
Responses
200Updated personalization401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/personalizations/exp_123/pause \
-H "Authorization: Bearer <apex-secret-key>"Archive personalization
/personalizations/{id}/archive
Responses
200Updated personalization401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/personalizations/exp_123/archive \
-H "Authorization: Bearer <apex-secret-key>"List assets
/assets
Parameters
folderin query - Filter to a Brand folder; Brand includes all its subfolders.include_archivedin query - Include archived assets.limitin querycursorin query
Responses
200Assets with cursor pagination400401403429503
curl \
https://app.drip-apex.com/api/v1/assets \
-H "Authorization: Bearer <apex-secret-key>"Upload assets
/assets
Responses
201Uploaded assets400401403413Too many files or a file exceeds the upload size limit.429503
curl -X POST \
https://app.drip-apex.com/api/v1/assets \
-H "Authorization: Bearer <apex-secret-key>"Get asset
/assets/{id}
Parameters
idin path required
Responses
200Asset401403404429503
curl \
https://app.drip-apex.com/api/v1/assets/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Archive asset
/assets/{id}
Archives the asset without deleting its stored file; it can be restored.
Parameters
idin path required
Responses
200Deleted401403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/assets/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Restore asset
/assets/{id}/restore
Parameters
idin path required
Responses
200Restored asset401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/assets/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"Read cost settings
/cost-settings
Parameters
include_retiredin query
Responses
200Cost settings400401403404409429503
curl \
https://app.drip-apex.com/api/v1/cost-settings \
-H "Authorization: Bearer <apex-secret-key>"Change cost settings
/cost-settings
Request body
{ $ref: "CostSettingsPatchInput" }Responses
200Updated cost settings400401403404409429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/cost-settings \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Read data-deletion status
/data-deletion
Returns the most recent deletion request for this shop that matches the supplied identifier. Supply at least one non-empty identifier: request_id or visitor_id. A request with neither, or with only empty values, returns 400. Both may be supplied; request_id then takes precedence and visitor_id is ignored. No match returns 404.
Parameters
request_idin query - Id of a deletion request, as returned by POST. Takes precedence over visitor_id when both are non-empty.visitor_idin query - Visitor id; selects the visitor's most recent deletion request. Used only when request_id is absent or empty.
Responses
200Deletion status400401403404429503
curl \
https://app.drip-apex.com/api/v1/data-deletion \
-H "Authorization: Bearer <apex-secret-key>"Request data deletion
/data-deletion
Request body
{
visitor_id: string
}Responses
202The request is recorded when accepted. Deletion usually finishes before the response. While recent event writes for the shop are still settling, it can stay queued for up to about an hour. Copies of erased data that arrive later are found and removed by follow-up checks. Deletion completes within the 30 days promised in the DPA.400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/data-deletion \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"visitor_id": "example_visitor_id"
}'Export personal data
/data-export
Returns the most recent export request for this shop that matches the supplied identifier. Supply at least one non-empty identifier: request_id or visitor_id. A request with neither, or with only empty values, returns 400. Both may be supplied; request_id then takes precedence and visitor_id is ignored. No match returns 404.
Parameters
request_idin query - Id of a export request, as returned by POST. Takes precedence over visitor_id when both are non-empty.visitor_idin query - Visitor id; selects the visitor's most recent export request. Used only when request_id is absent or empty.
Responses
200Data export400401403404429503
curl \
https://app.drip-apex.com/api/v1/data-export \
-H "Authorization: Bearer <apex-secret-key>"Request a personal data export
/data-export
Request body
{
limit?: integer
visitor_id: string
}Responses
200Completed data export400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/data-export \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"limit": 1,
"visitor_id": "example_visitor_id"
}'List exclusion groups
/exclusion-groups
Parameters
limitin querycursorin queryinclude_archivedin query - Include archived exclusion groups.
Responses
200exclusion groups list400401403404429503
curl \
https://app.drip-apex.com/api/v1/exclusion-groups \
-H "Authorization: Bearer <apex-secret-key>"Create exclusion groups
/exclusion-groups
Request body
{ $ref: "ExclusionGroupInput" }Responses
201exclusion groups created400401403404409Retained checkout history refuses membership writes with checkout_mode_retired.422Constraint codes: min_members, duplicate_member, invalid_share, totals_mismatch, invalid_experiment, archived_member, or membership_conflict.429503
curl -X POST \
https://app.drip-apex.com/api/v1/exclusion-groups \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get exclusion group
/exclusion-groups/{id}
Parameters
idin path required
Responses
200exclusion group returned400401403404429503
curl \
https://app.drip-apex.com/api/v1/exclusion-groups/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update exclusion group
/exclusion-groups/{id}
Parameters
idin path required
Request body
{ $ref: "ExclusionGroupUpdateInput" }Responses
200exclusion group returned400401403404409Retained checkout history refuses membership writes with checkout_mode_retired.422Constraint codes: min_members, duplicate_member, invalid_share, totals_mismatch, invalid_experiment, archived_member, or membership_conflict.429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/exclusion-groups/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive exclusion group
/exclusion-groups/{id}
Archives the group while preserving membership.
Parameters
idin path required
Responses
200exclusion group deleted400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/exclusion-groups/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Replace exclusion group members
/exclusion-groups/{id}/members
Parameters
idin path required
Request body
{ $ref: "ExclusionGroupMembersInput" }Responses
200Exclusion group members replaced400401403404409Retained checkout history refuses membership writes with checkout_mode_retired.422Constraint codes: min_members, duplicate_member, invalid_share, totals_mismatch, invalid_experiment, archived_member, or membership_conflict.429503
curl -X PUT \
https://app.drip-apex.com/api/v1/exclusion-groups/exp_123/members \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Restore exclusion group
/exclusion-groups/{id}/restore
Parameters
idin path required
Responses
200Exclusion group restored400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/exclusion-groups/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"Explain experiment results
/experiments/{id}/results/explain
Diagnostic classifier, not the canonical results pipeline. Labels in precedence order: bot, qa_traffic, outside_window, duplicate, before_exposure, exposed_before_window_only, not_exposed, filtered_by_goal, counted. Residuals expose rule differences between the diagnostic and the results pipeline. Reads are bounded to the result window plus 30 prior days; timeouts never return partial results.
Parameters
idin path requiredgoalin query - Attached goal ID or event key; defaults to primary.currencyin queryorderin query - Restrict candidate rows to this order and exposures to its visitors; include its label trail across known arms. Reported values and residuals are omitted (null) in order mode.
Responses
200Diagnostic explanation400Unknown goal or invalid query401403404409No result window422diagnostic_row_limit: more than 100000 conversion candidates; error.details includes observed row count and a hint to narrow by --order.429502invalid_raw_timestamp; raw analytics data could not be read503504tinybird_timeout; no partial diagnostic
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/results/explain \
-H "Authorization: Bearer <apex-secret-key>"Generate external purchase pixel snippet
/external-pixels/snippet
Requires a secret API key with the external_pixels:write scope; the response embeds a signed ingest credential, so read-scoped and publishable keys receive 403 Forbidden.
Request body
{
globalName?: string = "apexExternalPurchase"
handoverParam?: string = "drip_bucket"
provider?: string = "external"
}Responses
200Generated snippet400401403429500The server cannot sign the snippet's ingest credential because signing is not configured, or an unexpected server error occurred. Unexpected errors carry error.details.503
curl -X POST \
https://app.drip-apex.com/api/v1/external-pixels/snippet \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"globalName": "example_globalName",
"handoverParam": "example_handoverParam",
"provider": "example_provider"
}'List flags
/flags
Parameters
limitin querycursorin query
Responses
200Flags with cursor pagination400401403404429503
curl \
https://app.drip-apex.com/api/v1/flags \
-H "Authorization: Bearer <apex-secret-key>"Create flag
/flags
Request body
{ $ref: "FeatureFlagInput" }Responses
201Created flag400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get flag
/flags/{id}
Parameters
idin path required
Responses
200Flag401403404429503
curl \
https://app.drip-apex.com/api/v1/flags/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update flag
/flags/{id}
Parameters
idin path required
Request body
{ $ref: "FeatureFlagUpdateInput" }Responses
200Updated flag400401403404429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/flags/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Activate flag
/flags/{id}/activate
Parameters
idin path required
Responses
200Updated flag401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/activate \
-H "Authorization: Bearer <apex-secret-key>"Archive flag
/flags/{id}/archive
Parameters
idin path required
Responses
200Updated flag401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/archive \
-H "Authorization: Bearer <apex-secret-key>"Update flag On variant code
/flags/{id}/code
Edits only the On variant code through artifact validation and review. Omitted code is preserved; null clears it. The precondition refers to the On variant. A 502 receipt with saved=true means persistence succeeded but publication failed; do not blindly retry. needs_review is advisory for an already active flag.
Parameters
idin path required
Request body
{
custom_css?: string | null
custom_js?: string | null
expected_artifact_hash?: string
expected_updated_at?: string (date-time)
}Responses
200Code saved; publication status is reported separately400Validation or artifact review refused the save401403404409429502Code saved but storefront publication failed503
curl -X PATCH \
https://app.drip-apex.com/api/v1/flags/exp_123/code \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"custom_css": "example_custom_css",
"custom_js": "example_custom_js",
"expected_artifact_hash": "example_expected_artifact_hash",
"expected_updated_at": "example_expected_updated_at"
}'Conclude flag with a winning variant
/flags/{id}/conclude
Parameters
idin path required
Request body
{
variant_key: string
}Responses
200Concluded flag400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/conclude \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"variant_key": "example_variant_key"
}'Create a rule experience
/flags/{id}/experiences
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path required
Request body
{
anti_flicker_selectors?: string[]
code_delivery_mode?: "inline" | "external"
custom_css?: string | null
custom_js?: string | null
environment_key?: string
expected_updated_at: string (date-time)
key: string
mutations?: any[]
name: string
reset_js?: string | null
value?: any
}Responses
201Updated flag400401403404409422429502Experience saved but publication failed. The error details preserve the saved receipt.503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/experiences \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_updated_at": "example_expected_updated_at",
"key": "example_key",
"name": "example_name"
}'Update a rule experience
/flags/{id}/experiences/{experienceId}
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path requiredexperienceIdin path required
Request body
{
anti_flicker_selectors?: string[]
code_delivery_mode?: "inline" | "external"
custom_css?: string | null
custom_js?: string | null
environment_key?: string
expected_updated_at: string (date-time)
key?: string
mutations?: any[]
name?: string
reset_js?: string | null
value?: any
}Responses
200Updated flag400401403404409422429502Experience saved but publication failed. The error details preserve the saved receipt.503
curl -X PATCH \
https://app.drip-apex.com/api/v1/flags/exp_123/experiences/experience_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_updated_at": "example_expected_updated_at"
}'Delete an unreferenced rule experience
/flags/{id}/experiences/{experienceId}
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path requiredexperienceIdin path required
Request body
{
environment_key?: string
expected_updated_at: string (date-time)
}Responses
200Updated flag400401403404409422429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/flags/exp_123/experiences/experience_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"environment_key": "example_environment_key",
"expected_updated_at": "example_expected_updated_at"
}'Pause flag
/flags/{id}/pause
Parameters
idin path required
Responses
200Updated flag401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/pause \
-H "Authorization: Bearer <apex-secret-key>"List flag QA devices
/flags/{id}/qa-devices
Parameters
idin path required
Responses
200Active QA devices400401403404429503
curl \
https://app.drip-apex.com/api/v1/flags/exp_123/qa-devices \
-H "Authorization: Bearer <apex-secret-key>"Register or update a flag QA device
/flags/{id}/qa-devices
Parameters
idin path required
Request body
{ $ref: "FlagQaDeviceInput" }Responses
200Registered QA device400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/qa-devices \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Remove a flag QA device
/flags/{id}/qa-devices/{identity}
Parameters
idin path requiredidentityin path required
Responses
200Removed400401403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/flags/exp_123/qa-devices/example_identity \
-H "Authorization: Bearer <apex-secret-key>"Reopen a concluded flag
/flags/{id}/reopen
Parameters
idin path required
Responses
200Reopened flag400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/reopen \
-H "Authorization: Bearer <apex-secret-key>"Replace ordered flag audiences
/flags/{id}/rules
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path required
Request body
{
environment_key?: string
expected_updated_at: string (date-time)
rules: object[]
}Responses
200Updated flag400401403404409422429503
curl -X PUT \
https://app.drip-apex.com/api/v1/flags/exp_123/rules \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"environment_key": "example_environment_key",
"expected_updated_at": "example_expected_updated_at",
"rules": []
}'Reorder flag audiences
/flags/{id}/rules/reorder
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path required
Request body
{
environment_key?: string
expected_updated_at: string (date-time)
rules: object[]
}Responses
200Updated flag400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/flags/exp_123/rules/reorder \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"environment_key": "example_environment_key",
"expected_updated_at": "example_expected_updated_at",
"rules": []
}'Get flag edge publication status
/flags/{id}/status
Returns database publication metadata, the propagation worst-case window derived from the shop's effective runtime cache TTLs, and, for visual flags, the public edge config URL used to confirm propagation. API-delivered flags have no public config URL and are available through /v1/assign.
Parameters
idin path required
Responses
200Flag publication status401403404429503
curl \
https://app.drip-apex.com/api/v1/flags/exp_123/status \
-H "Authorization: Bearer <apex-secret-key>"List funnels
/funnels
Parameters
limitin querycursorin queryinclude_archivedin query - Include archived funnels.
Responses
200funnels list400401403404429503
curl \
https://app.drip-apex.com/api/v1/funnels \
-H "Authorization: Bearer <apex-secret-key>"Create funnels
/funnels
Request body
{ $ref: "FunnelInput" }Responses
201funnels created400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/funnels \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get funnel
/funnels/{id}
Parameters
idin path required
Responses
200funnel returned400401403404429503
curl \
https://app.drip-apex.com/api/v1/funnels/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update funnel
/funnels/{id}
Parameters
idin path required
Request body
{ $ref: "FunnelUpdateInput" }Responses
200funnel returned400401403404409422429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/funnels/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive funnel
/funnels/{id}
Archives the funnel instead of destroying it.
Parameters
idin path required
Responses
200funnel deleted400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/funnels/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Restore funnel
/funnels/{id}/restore
Parameters
idin path required
Responses
200Restored funnel400401403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/funnels/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"List landing pages
/landing-pages
Requires the Apex Labs feature for the shop; shops without it receive 404 Not Found.
Parameters
limitin querycursorin querystatusin query
Responses
200Landing pages with cursor pagination400401403404Apex Labs is not enabled for this shop, so landing pages are unavailable.429503
curl \
https://app.drip-apex.com/api/v1/landing-pages \
-H "Authorization: Bearer <apex-secret-key>"Create landing page
/landing-pages
Creates a landing page to build outside Apex: pull its checkout, apply a bundle, deploy. With idea_key the page records the idea, and experiments created on it link to that idea. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Request body
{
idea_key?: string
name: string
slug?: string
}Responses
201Landing page created400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"idea_key": "example_idea_key",
"name": "example_name",
"slug": "example_slug"
}'List landing-page domains
/landing-pages/domains
Requires the landing-pages entitlement for the shop; shops without it receive 404 Not Found. Returns the shop's claimed and registered hostnames, newest first, without removed ones. At most 200; has_more is true when the list was cut. Pass host to look up one hostname.
Parameters
hostin query - Return only this hostname, canonicalized as the server stores it (lowercase, punycode, no scheme, port or path). Finds a record the 200-row list cut, and a removed record whose Cloudflare hostname delete has not finished (status removed), so DELETE can be retried.
Responses
200Landing-page domains, newest first400host is not a valid hostname (invalid_host)401403404429503
curl \
https://app.drip-apex.com/api/v1/landing-pages/domains \
-H "Authorization: Bearer <apex-secret-key>"Claim or register a landing-page domain
/landing-pages/domains
Requires the landing-pages entitlement for the shop; shops without it receive 404 Not Found. mode claim adds a hostname the client controls: point a CNAME at dns_target, then call verify. Claiming the same hostname again returns the existing record (200). mode register buys a new domain on the Drip Cloudflare account, billed to the client on the next DRIP invoice, and needs a user key whose user can write landing pages (owner, admin or editor); Drip staff are notified of every purchase: the first call answers 409 with the quote, and a repeat with confirm_price equal to the quote registers it with auto-renew and WHOIS redaction on. Repeating a register call for a domain this shop already registered resumes its setup and never buys twice.
Request body
{
confirm_price?: object
host: string
mode: "claim" | "register"
years?: integer = 1
}Responses
200Existing record (repeat claim, or resumed registration)201Created record with DNS instructions400401403404409Conflict or price confirmation required429502Cloudflare refused or failed the hostname or registrar call: registrar_refused (Cloudflare rejected the order, nothing was charged, the domain can be ordered again) or registrar_failed (the outcome is unknown; run verify)503Cloudflare Registrar is not configured (registrar_unconfigured), the token lacks Registrar permission (registrar_permission_missing), or the database is briefly busy (database_busy, with Retry-After)
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/domains \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"confirm_price": {},
"host": "example_host",
"mode": "claim",
"years": 1
}'Remove a landing-page domain
/landing-pages/domains/{id}
Requires the landing-pages entitlement for the shop; shops without it receive 404 Not Found. Deletes the Cloudflare custom hostname and marks the record removed. Refuses with 409 domain_in_use while a published landing page serves the host, and with 409 domain_registration_in_progress while a registration order is unsettled (run verify first). For a charged registration it writes the rebilling audit first and answers 503 domain_audit_pending while that write fails. A registered domain stays registered on the Drip Cloudflare account. Removing a removed record returns it unchanged.
Parameters
idin path required
Responses
200The removed record401403404409429502Cloudflare could not delete the custom hostname503
curl -X DELETE \
https://app.drip-apex.com/api/v1/landing-pages/domains/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Check a landing-page domain
/landing-pages/domains/{id}/verify
Requires the landing-pages entitlement for the shop; shops without it receive 404 Not Found. Reads the Cloudflare custom hostname, the registration workflow (mode register) and public DNS, then records the status: pending (no CNAME to the edge yet), verifying (CNAME seen, hostname or certificate not active), active, or failed. For a registered domain it also finishes setup steps an earlier call could not complete; it never orders a domain.
Parameters
idin path required
Responses
200The record with its current status401403404409429502Cloudflare could not provision the custom hostname503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/domains/exp_123/verify \
-H "Authorization: Bearer <apex-secret-key>"Get landing page
/landing-pages/{id}
Parameters
idin path required
Responses
200Landing page401403404429503
curl \
https://app.drip-apex.com/api/v1/landing-pages/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Deploy a landing page version
/landing-pages/{id}/deploy
Publishes a version built outside Apex to the body host or the manifest host (a domain this shop holds), else to the shop's own instant host <shop>.apex-page.com, which the first such deploy assigns; another shop's apex-page.com host is a 400. It upserts its manifest goals and keeps the newest 20 unpublished versions. The Worker injects the shop snippet at serve time unless the manifest turns it off. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
host?: string
path?: string
version_id?: string (uuid)
}Responses
200Deployed400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/deploy \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"host": "example_host",
"path": "example_path",
"version_id": "uuid-here"
}'Create a landing page experiment
/landing-pages/{id}/experiment
Creates a draft experiment that splits the page's live URL between two versions, with the manifest goals attached (the first, or primary_goal, as primary) and the page's idea linked. Starting it is done in the dashboard. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
control: integer
name?: string
primary_goal?: string
variant: integer
}Responses
201Experiment created400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/experiment \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"control": 1,
"name": "example_name",
"primary_goal": "example_primary_goal",
"variant": 1
}'Pull a landing page checkout
/landing-pages/{id}/filesystem
Without paths: the current version's apex-landing-page.json, its file list (path, sha256, bytes) and the context folder (brand, catalog, offers, reviews, brief, idea). With paths: those files' contents, utf8 for text and base64 for binary files. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
pathsin query - Comma-separated file paths whose contents to return (at most 32 paths and 3145728 bytes). Without it the response lists files and carries the manifest and context.idin path required
Responses
200Checkout400401403404409413Requested files exceed one response429503
curl \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/filesystem \
-H "Authorization: Bearer <apex-secret-key>"Apply a landing page bundle
/landing-pages/{id}/filesystem/apply
Stores the bundle as a new version, makes it current, upserts the manifest goals and returns the version and a signed preview link. Files arrive inline, unchanged from the current version, or staged through filesystem/upload. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
base_version_id?: string (uuid) | null
files: object[]
manifest: object
}Responses
201Applied version400401403404409413Bundle exceeds 300 files or 100 MB429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/filesystem/apply \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"files": [],
"manifest": {}
}'Plan a landing page bundle
/landing-pages/{id}/filesystem/plan
Validates a bundle and its manifest and returns what an apply would change (added, changed, removed files, manifest and goal changes, files to upload first). Writes nothing. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
base_version_id?: string (uuid) | null
files: object[]
manifest: object
}Responses
200Plan400401403404413Bundle exceeds 300 files or 100 MB429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/filesystem/plan \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"files": [],
"manifest": {}
}'Upload landing page files
/landing-pages/{id}/filesystem/upload
Stages file bytes, keyed by sha256, for a following apply. Upload the files a plan lists under upload_required in batches of at most 32 files and 3 MB. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
files: object[]
}Responses
201Staged files400401403404413Batch exceeds 32 files or 3 MB429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/filesystem/upload \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"files": []
}'Grant a direct video upload
/landing-pages/{id}/filesystem/upload-grant
Issues a 15-minute single-file grant bound to shop, page, base version, path, byte size and SHA-256. MP4 and WebM over 3 MB and up to 50 MB upload directly to the Worker using resumable 8 MB multipart parts. Only hash-verified bytes can be applied. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
base_version_id: string (uuid) | null
bytes: integer
path: string
sha256: string
}Responses
201Video upload grant400401403404409Base version changed or page archived429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/filesystem/upload-grant \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"bytes": 1,
"path": "example_path",
"sha256": "example_sha256"
}'Get a landing page preview link
/landing-pages/{id}/preview
A fresh signed preview link (valid 24 hours, no dashboard login) for the current version, or for the version number in ?version=. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path requiredversionin query
Responses
200Preview link400401403404429503
curl \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/preview \
-H "Authorization: Bearer <apex-secret-key>"Roll back a landing page
/landing-pages/{id}/rollback
Deploys an earlier version to the page's current host and path. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{
version_id?: string (uuid)
version_number?: integer
}Responses
200Deployed400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/rollback \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"version_id": "uuid-here",
"version_number": 1
}'List landing page versions
/landing-pages/{id}/versions
Every version, newest first, with its source (generator or bundle), author, size and whether it is current or published. Requires the landing-pages feature for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Responses
200Versions400401403404429503
curl \
https://app.drip-apex.com/api/v1/landing-pages/exp_123/versions \
-H "Authorization: Bearer <apex-secret-key>"List pages
/pages
Parameters
limitin querycursorin query
Responses
200pages list400401403404429503
curl \
https://app.drip-apex.com/api/v1/pages \
-H "Authorization: Bearer <apex-secret-key>"Create pages
/pages
Request body
{ $ref: "PageInput" }Responses
201pages created400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/pages \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get page
/pages/{id}
Parameters
idin path required
Responses
200page returned400401403404429503
curl \
https://app.drip-apex.com/api/v1/pages/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update page
/pages/{id}
Parameters
idin path required
Request body
{ $ref: "PageUpdateInput" }Responses
200page returned400401403404409Conflict. A live-consumer acknowledgement conflict returns the PageLiveImpactError shape — repeat the request with confirm_live_impact and confirmed_consumer_ids carrying every id listed. A trigger-propagation race or duplicate API name returns the generic Error shape.422429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/pages/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Delete page
/pages/{id}
Parameters
idin path required
Responses
200page deleted400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/pages/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"List segments
/segments
Parameters
limitin querycursorin query
Responses
200segments list400401403404429503
curl \
https://app.drip-apex.com/api/v1/segments \
-H "Authorization: Bearer <apex-secret-key>"Create segments
/segments
Request body
{ $ref: "SegmentInput" }Responses
201segments created400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/segments \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Update segment
/segments/{id}
Parameters
idin path required
Request body
{ $ref: "SegmentUpdateInput" }Responses
200segment returned400401403404409Atomic refusal: a blocked consumer, concurrent edit, or more than 200 consumer expansions in one transaction. Limit refusals use segment_propagation_limit; nothing is saved or queued.422429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/segments/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Delete segment
/segments/{id}
Parameters
idin path required
Responses
200segment deleted400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/segments/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"List signals
/signals
The first page includes the built-in signal catalog before paginated custom signals. Built-ins do not participate in cursor pagination.
Parameters
limitin querycursorin queryinclude_archivedin query - Include archived custom signals when true. Built-in signals are always active.
Responses
200Paginated signals400401403429503
curl \
https://app.drip-apex.com/api/v1/signals \
-H "Authorization: Bearer <apex-secret-key>"Create a signal
/signals
Request body
{ $ref: "SignalCreate" }Responses
201Created400401403409429503
curl -X POST \
https://app.drip-apex.com/api/v1/signals \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get a signal
/signals/{id}
Parameters
idin path required - Custom signal UUID or built-in catalog signal key (e.g. url.full).
Responses
200Resource401403404429503
curl \
https://app.drip-apex.com/api/v1/signals/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update a signal
/signals/{id}
Parameters
idin path required - Custom signal UUID or built-in catalog signal key (e.g. url.full).
Request body
{ $ref: "SignalUpdate" }Responses
200Updated400401403404409429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/signals/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Delete or archive a signal
/signals/{id}
Parameters
idin path required - Custom signal UUID or built-in catalog signal key (e.g. url.full).
Responses
200Deleted or archived401403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/signals/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"List surveys
/surveys
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
limitin querycursorin querystatusin query
Responses
200Surveys400401403404429503
curl \
https://app.drip-apex.com/api/v1/surveys \
-H "Authorization: Bearer <apex-secret-key>"Create survey
/surveys
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Request body
{ $ref: "SurveyInput" }Responses
201Created survey400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/surveys \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get survey
/surveys/{id}
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Responses
200Survey400401403404409429503
curl \
https://app.drip-apex.com/api/v1/surveys/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Delete survey
/surveys/{id}
Requires the Surveys lab for the shop; shops without it receive 404 Not Found. Deletes a draft or completed survey; live and paused surveys must be ended first.
Parameters
idin path required
Responses
200Deleted survey400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/surveys/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update survey
/surveys/{id}
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Request body
{ $ref: "SurveyUpdateInput" }Responses
200Updated survey400401403404409429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/surveys/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Complete survey
/surveys/{id}/complete
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Responses
200Survey400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/surveys/exp_123/complete \
-H "Authorization: Bearer <apex-secret-key>"Duplicate survey
/surveys/{id}/duplicate
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Responses
201Survey400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/surveys/exp_123/duplicate \
-H "Authorization: Bearer <apex-secret-key>"Pause survey
/surveys/{id}/pause
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Responses
200Survey400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/surveys/exp_123/pause \
-H "Authorization: Bearer <apex-secret-key>"Start survey
/surveys/{id}/start
Requires the Surveys lab for the shop; shops without it receive 404 Not Found.
Parameters
idin path required
Responses
200Survey400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/surveys/exp_123/start \
-H "Authorization: Bearer <apex-secret-key>"Preview brand completion undo
/api/multi-shop-programs/{id}/actions/undo-completion/preview
Not yet enabled: both routes return 404 while the server rollout is off. Dashboard session, required initiating shop pin, program access and per-store write/launch rights. Explicit all_with_source includes the source once; all means copies only; shop_id selects source or one copy. Preview is read-only. Apply binds actor, pin, policy, exact targets and program membership. Structural conflicts before execution write nothing. Later refusals produce partial receipts; each target commits independently. A same-key replay never repeats committed lifecycle work. Re-completion requires a new preview and key.
Parameters
idin path requiredx-apex-shopin header required
Request body
{
scope?: "all" | "all_with_source"
shop_id?: string (uuid)
}Responses
200Per-store eligibility and redacted no-access targets400Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.401Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.403Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.404Not found, including while server rollout is off.409Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.500Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.503Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.
curl -X POST \
https://app.drip-apex.com/api/v1/api/multi-shop-programs/exp_123/actions/undo-completion/preview \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"scope": "all",
"shop_id": "uuid-here"
}'Undo completion independently in each reviewed store
/api/multi-shop-programs/{id}/actions/undo-completion
Not yet enabled: both routes return 404 while the server rollout is off. Dashboard session, required initiating shop pin, program access and per-store write/launch rights. Explicit all_with_source includes the source once; all means copies only; shop_id selects source or one copy. Preview is read-only. Apply binds actor, pin, policy, exact targets and program membership. Structural conflicts before execution write nothing. Later refusals produce partial receipts; each target commits independently. A same-key replay never repeats committed lifecycle work. Re-completion requires a new preview and key.
Parameters
idin path requiredx-apex-shopin header required
Request body
{
scope?: "all" | "all_with_source"
shop_id?: string (uuid)
reason: string
idempotency_key: string (uuid)
preview_version: string
targets: object[]
}Responses
200Durable store receipts, including partial or fully blocked results400Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.401Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.403Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.404Not found, including while server rollout is off.409Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.500Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.503Typed refusal. Errors after partial work carry committed:true and durable, access-redacted receipts.
curl -X POST \
https://app.drip-apex.com/api/v1/api/multi-shop-programs/exp_123/actions/undo-completion \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"reason": "example_reason",
"idempotency_key": "uuid-here",
"preview_version": "example_preview_version",
"targets": []
}'Create a rule experience
/api/flags/{id}/experiences
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path required
Request body
{
anti_flicker_selectors?: string[]
code_delivery_mode?: "inline" | "external"
custom_css?: string | null
custom_js?: string | null
environment_key?: string
expected_updated_at: string (date-time)
key: string
mutations?: any[]
name: string
reset_js?: string | null
value?: any
}Responses
201Updated flag400Request refused401Request refused403Request refused404Request refused409Request refused422Request refused429Request refused502Request refused503Request refused
curl -X POST \
https://app.drip-apex.com/api/v1/api/flags/exp_123/experiences \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_updated_at": "example_expected_updated_at",
"key": "example_key",
"name": "example_name"
}'Update a rule experience
/api/flags/{id}/experiences/{experienceId}
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path requiredexperienceIdin path required
Request body
{
anti_flicker_selectors?: string[]
code_delivery_mode?: "inline" | "external"
custom_css?: string | null
custom_js?: string | null
environment_key?: string
expected_updated_at: string (date-time)
key?: string
mutations?: any[]
name?: string
reset_js?: string | null
value?: any
}Responses
200Updated flag400Request refused401Request refused403Request refused404Request refused409Request refused422Request refused429Request refused502Request refused503Request refused
curl -X PATCH \
https://app.drip-apex.com/api/v1/api/flags/exp_123/experiences/experience_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_updated_at": "example_expected_updated_at"
}'Delete an unreferenced rule experience
/api/flags/{id}/experiences/{experienceId}
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path requiredexperienceIdin path required
Request body
{
environment_key?: string
expected_updated_at: string (date-time)
}Responses
200Updated flag400Request refused401Request refused403Request refused404Request refused409Request refused422Request refused429Request refused503Request refused
curl -X DELETE \
https://app.drip-apex.com/api/v1/api/flags/exp_123/experiences/experience_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"environment_key": "example_environment_key",
"expected_updated_at": "example_expected_updated_at"
}'Replace ordered flag audiences
/api/flags/{id}/rules
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path required
Request body
{
environment_key?: string
expected_updated_at: string (date-time)
rules: object[]
}Responses
200Updated flag400Request refused401Request refused403Request refused404Request refused409Request refused422Request refused429Request refused503Request refused
curl -X PUT \
https://app.drip-apex.com/api/v1/api/flags/exp_123/rules \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"environment_key": "example_environment_key",
"expected_updated_at": "example_expected_updated_at",
"rules": []
}'Reorder flag audiences
/api/flags/{id}/rules/reorder
Uses the parent flag expected_updated_at. Stale versions and archived flags return 409. Rule removals retain history. Referenced experiences cannot be deleted. The response is the complete updated flag. Experience saves include code_save with artifact review and publication receipts. A 502 error.details receipt with saved=true means the write committed but publication failed; reload before retrying. Rule saves report publication_warning separately.
Parameters
idin path required
Request body
{
environment_key?: string
expected_updated_at: string (date-time)
rules: object[]
}Responses
200Updated flag400Request refused401Request refused403Request refused404Request refused409Request refused422Request refused429Request refused503Request refused
curl -X POST \
https://app.drip-apex.com/api/v1/api/flags/exp_123/rules/reorder \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"environment_key": "example_environment_key",
"expected_updated_at": "example_expected_updated_at",
"rules": []
}'Undo completion in the same run
/api/experiments/{id}/undo-completion
Not yet enabled: both routes return 404 while the server rollout is off. Dashboard session only. Requires experiments:write and experiments:launch for running restoration. Preview never writes. Applies re-read policy and bind actor, shop, completion, definition, history and membership versions. Committed refresh failures remain HTTP 200; retry the same key for publication only. Queued retries and in-flight external sends may still deliver.
Parameters
idin path requiredx-apex-shopin header required
Request body
{
expected_assignment_epoch: integer
preview_version: string
completion_id: string (uuid)
idempotency_key: string (uuid)
reason: string
}Responses
200Durable commit receipt or idempotent replay400invalid_request / shop_pin_required401unauthenticated403no_permission / member_suspended404experiment_not_found409undo_window_expired / undo_disabled / serving_derivative_exists / configuration_changed / assignment_epoch_mismatch / scheduled_end_in_past / resume_blocked / experiment_changed / run_changed / preview_changed / policy_changed / program_deployment_moved / completion_scope_mismatch / completion_evidence_missing / completion_not_last_lifecycle_event / run_not_closed_by_completion / completion_timestamp_missing / status_not_completed / experiment_archived / program_archived / program_action_in_progress / action_in_progress / idempotency_key_mismatch503policy_unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/api/experiments/exp_123/undo-completion \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_assignment_epoch": 1,
"preview_version": "example_preview_version",
"completion_id": "uuid-here",
"idempotency_key": "uuid-here",
"reason": "example_reason"
}'Preview same-run completion undo
/api/experiments/{id}/undo-completion/preview
Not yet enabled: both routes return 404 while the server rollout is off. Dashboard session only. Requires experiments:write and experiments:launch for running restoration. Preview never writes. Applies re-read policy and bind actor, shop, completion, definition, history and membership versions. Committed refresh failures remain HTTP 200; retry the same key for publication only. Queued retries and in-flight external sends may still deliver.
Parameters
idin path requiredx-apex-shopin header required
Request body
{
expected_assignment_epoch: integer
}Responses
200Eligible or blocked read-only preview400invalid_request / shop_pin_required401unauthenticated403no_permission / member_suspended404experiment_not_found409undo_window_expired / undo_disabled / serving_derivative_exists / configuration_changed / assignment_epoch_mismatch / scheduled_end_in_past / resume_blocked / experiment_changed / run_changed / preview_changed / policy_changed / program_deployment_moved / completion_scope_mismatch / completion_evidence_missing / completion_not_last_lifecycle_event / run_not_closed_by_completion / completion_timestamp_missing / status_not_completed / experiment_archived / program_archived / program_action_in_progress / action_in_progress / idempotency_key_mismatch503policy_unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/api/experiments/exp_123/undo-completion/preview \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_assignment_epoch": 1
}'Experiments
Attach or upload QA evidence
/experiments/{id}/qa/evidence
Attaches a terminal screenshot job, writes typed evidence records, or uploads QA evidence images. Exactly one request form is accepted per call.
Request body
anyResponses
200Signed QA run with the updated evidence400401403404409429502Evidence upload failed503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/qa/evidence \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Create an artifact-injecting proxy preview session
/preview-sessions
Requires a secret API key with experiments:write. The returned short-lived URL is a bearer capability that renders the selected draft variation without a dashboard session or installed Apex snippet.
Request body
{
experiment_id: string
variation_id?: string
target_url?: string (uri)
ttl_hours?: integer = 168
}Responses
200Preview session400401403404409QA run changed during preview; retry429503
curl -X POST \
https://app.drip-apex.com/api/v1/preview-sessions \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"experiment_id": "example_experiment_id",
"variation_id": "example_variation_id",
"target_url": "https://example.com",
"ttl_hours": 168
}'List experiments
/experiments
Returns a paginated list of experiments. Supports cursor-based pagination and optional status filter.
Parameters
viewin query - Full retains the existing response; summary omits code, targeting objects and goals unless fields requests a full-view field, which switches automatically to full details.fieldsin query - Comma-separated top-level keys. Always keeps id. Fields outside summary switch automatically to full details. Unknown keys return 400 with allowed keys.statusin queryurlin query - Absolute storefront URL; returns only experiments whose effective URL targeting matches it, evaluated server-side. Experiments with no URL rules match every URL.archivedin query - Set to 1 to include archived experimentslimitin querycursorin query - ID of the last item from the previous page
Responses
200Paginated list of experiments400invalid_view, invalid_fields, or invalid_status; the error lists allowed values.401Unauthorized429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments \
-H "Authorization: Bearer <apex-secret-key>"Create experiment
/experiments
Creates a new experiment. Default control and variant are created if variations are not provided. Omitted statistical settings use the shop testing defaults. Experiments authenticated with a user API key default owner_user_id to that key's user; organization- and shop-scoped keys have no user and leave the owner null. When commerce sentinel mutations are supplied without goals, the shop default takes priority, followed by the oldest active unscoped revenue goal. The first attached goal is primary. For mutation drafts with URL or page-rule targeting, omitted or null store_url triggers an observed-page inventory lookup with a 1.5-second budget before the database transaction. A matching observed URL is saved only if it still matches the final persisted targeting. If no matching page is available, including on lookup timeout, store_url remains null and the partial draft is still created. An explicit store_url takes precedence. Redirect creation keeps its existing entry-URL behavior.
Request body
{
name: string
slug?: string | null
description?: string | null
backlog_item_id?: string (uuid)
idea_key?: string
as_follow_up?: boolean = false
owner_user_id?: string (uuid) | null
counting_type?: "standard" | "setup_test" | "aa_test" | "non_program" = "standard"
delivery_type?: "mutation" | "redirect" = "mutation"
type?: "standard" | "setup_test" | "aa_test" | "non_program"
experiment_type?: "mutation" | "redirect"
traffic_allocation?: number = 1
experiment_js?: string | null
experiment_reset_js?: string | null
experiment_css?: string | null
sequential_testing_enabled?: boolean
significance_level?: number
statistical_power?: number
store_url?: string (uri) | null
auto_stop_override?: AutoStopOverride
guardian_override?: GuardianOverride
targeting?: ExperimentTargeting | null
variations?: object[]
primary_goal_id?: string (uuid) | null
goal_ids?: string (uuid)[]
expectedCommerceRevision?: integer | null
expectedArtifactHash?: string
definition?: object
}Responses
200Existing experiment already linked to the backlog item201Experiment created400Validation error401Unauthorized404Backlog item not found for this shop409commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422Backlog item requires customer approval or the dashboard landing-page handoff, or both backlog_item_id and idea_key were sent commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.429Rate limit exceeded503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "example_name"
}'Get experiment
/experiments/{id}
Returns a single experiment by ID with variations and goals.
Parameters
includein query - Pass preview_target to resolve and include the preview destination. Omitted by default to avoid an inventory lookup.idin path required
Responses
200Experiment details404Experiment not found
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update experiment
/experiments/{id}
Partially updates an experiment. Completed experiments accept only a window-only body (`results_weekdays`), which changes how the ended test is judged without editing the test. Audience and page targeting writes return 409 composition_locked while an experiment is running, completed or archived; the v1 API accepts other edits on draft experiments only, except a body that carries only `results_weekdays`, which it accepts on non-draft experiments. Unchanged launch-critical values preserve existing QA approval and scheduling. Material setup changes revoke launch readiness and write a same-transaction Activity audit with reason `launch_critical_edit`. If that required audit fails, the update fails and rolls back.
Parameters
idin path required
Request body
{
name?: string
slug?: string | null
description?: string | null
owner_user_id?: string (uuid) | null
results_weekdays?: ResultsWeekdays | null
counting_type?: "standard" | "setup_test" | "aa_test" | "non_program"
type?: "standard" | "setup_test" | "aa_test" | "non_program"
traffic_allocation?: number
experiment_js?: string | null
experiment_reset_js?: string | null
experiment_css?: string | null
sequential_testing_enabled?: boolean
store_url?: string (uri) | null
auto_stop_override?: AutoStopOverride
guardian_override?: GuardianOverride
targeting?: ExperimentTargeting | null
confirmTargetingReplace?: boolean
}Responses
200Experiment updated400Cannot update completed experiment (a window-only results_weekdays body is the exception)404Experiment not found409Targeting replacement requires confirmation (error.code confirm_targeting_replace_required): the update would replace the experiment's author-explicit URL targeting with a page-entity rule. Retry with confirmTargetingReplace: true. Retired historical targeting writes return 409 checkout_mode_retired.422Invalid experiment constraints.
curl -X PATCH \
https://app.drip-apex.com/api/v1/experiments/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive experiment
/experiments/{id}
Archives an experiment while preserving its variations and results history.
Parameters
idin path required
Responses
200Experiment deleted404Experiment not found
curl -X DELETE \
https://app.drip-apex.com/api/v1/experiments/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Read the experiment filesystem JSON Schema
/experiments/filesystem/schema
Returns the versioned JSON Schema document bundled by the Apex CLI.
Responses
200Experiment filesystem schema401Unauthorized403Forbidden429503
curl \
https://app.drip-apex.com/api/v1/experiments/filesystem/schema \
-H "Authorization: Bearer <apex-secret-key>"Read experiment filesystem
/experiments/{id}/filesystem
Returns the complete canonical filesystem snapshot for an experiment. Commerce drafts are represented as typed commerce/<kind>.json files with read-only serverOwned fields. commerce/discount-codes.json declares per-variation code blocking and optional en/de messages (1 to 120 characters); control never blocks. It combines with price, shipping and offer levers and requires the automatic offers discount owner at launch. File hashes can be used to detect changes before planning a write.
Parameters
idin path required
Responses
200Canonical experiment filesystem401Unauthorized404Experiment not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/filesystem \
-H "Authorization: Bearer <apex-secret-key>"Plan an experiment filesystem apply
/experiments/{id}/filesystem/plan
Validates a complete filesystem snapshot and reports the changes without writing them. Typed commerce files are draft-only and preserve server-owned carrier fields. Draft experiments accept all supported filesystem changes. Paused experiments accept treatment-content changes and redirect destinations. Running experiments accept treatment-content changes only; redirect destinations remain draft/paused-only. Structural changes remain draft-only and are reported with the offending file paths.
Parameters
idin path required
Request body
{
files: object[]
}Responses
200Filesystem apply plan400Invalid request body401Unauthorized404Experiment not found429Rate limit exceeded503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/filesystem/plan \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"files": []
}'Apply an experiment filesystem
/experiments/{id}/filesystem/apply
Atomically applies experiment, goal, variation, and variation-artifact changes to draft experiments. Paused experiments accept treatment-content changes and redirect destinations. Running experiments accept treatment-content changes only; redirect destinations remain draft/paused-only. Structural changes remain draft-only and fail closed with the offending file paths. Running artifact writes republish the storefront configuration. A successful response with applied=true means every database change committed. applied=false means no changes from this request committed, except when saved=true reports a committed artifact whose storefront publication failed. Identically re-applying an already committed snapshot is safe and converges on the same final state.
Parameters
idin path required
Request body
{
accept_qa_invalidation?: boolean
confirmTargetingReplace?: boolean
files: object[]
}Responses
200Atomic apply outcome400Invalid request body401Unauthorized404Experiment not found409Targeting replacement requires confirmation (error.code confirm_targeting_replace_required). Retry with confirmTargetingReplace: true. A fresh QA approval requires acknowledgement when accept_qa_invalidation is false (error.code qa_acknowledgment_required, error.details { qa_approved_at, required_acknowledgment }); retry with accept_qa_invalidation: true after reviewing the approval. No writes occur for either confirmation refusal. Retired historical targeting writes return 409 checkout_mode_retired.422Invalid experiment constraints.429Rate limit exceeded500Unexpected failure; the atomic database transaction was rolled back and no filesystem changes committed.503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/filesystem/apply \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"accept_qa_invalidation": false,
"confirmTargetingReplace": false,
"files": []
}'Get experiment results
/experiments/{id}/results
Returns the canonical experiment results read model, including totals, arms, decision, metrics, timeseries, warnings, and delivery health.
Parameters
idin path requiredcurrencyin query - Optional ISO currency code for revenue diagnostics.segmentin query - Include one built-in audience dim:value (for example device:mobile). Include wins over exclude_segment. Unknown dimensions, invalid or unobserved values fall back to the unsliced read with segment_applied.applied false and an explicit reason. One model build per request.exclude_segmentin query - Exclude one audience dim:value; ignored when segment is also present.
Responses
200Experiment results401Unauthorized403API key is missing experiment read permission404Experiment not found429Rate limit exceeded503The default snapshot or live results are still computing, or the database is temporarily busy.
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/results \
-H "Authorization: Bearer <apex-secret-key>"Get experiment by slug
/experiments/by-slug/{slug}
Fetch a single experiment by slug (scoped to the API key's shop). Archived variations are omitted unless include_archived=true.
Parameters
includein query - Pass preview_target to resolve and include the preview destination. Omitted by default to avoid an inventory lookup.include_archivedin query - Pass true to include archived variations and their archived_at timestamps.slugin path required
Responses
200Experiment details401Unauthorized404Experiment not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments/by-slug/example_slug \
-H "Authorization: Bearer <apex-secret-key>"Upsert experiment by slug
/experiments/by-slug/{slug}
Idempotently create or update a draft experiment by slug. New experiments use the shop testing defaults for omitted statistical settings and default owner_user_id to the authenticated user for user API keys; organization- and shop-scoped keys leave the owner null. If an experiment exists with this slug and is not in draft status, or an incoming variation slug belongs to an archived variation, returns 409. Unchanged launch-critical values preserve existing QA approval and scheduling. Material setup changes revoke launch readiness and write a same-transaction Activity audit with reason `launch_critical_edit`. If that required audit fails, the update fails and rolls back. On creation, commerce sentinel mutations without goals use the shop default, then the oldest active unscoped revenue goal; existing drafts keep their saved goals. Existing draft-only and replacement semantics still apply: preservation depends on the resulting material values, not merely on omitted fields.
Parameters
slugin path required
Request body
{
name: string
description?: string | null
counting_type?: "standard" | "setup_test" | "aa_test" | "non_program"
delivery_type?: "mutation" | "redirect"
type?: "standard" | "setup_test" | "aa_test" | "non_program"
experiment_type?: "mutation" | "redirect"
traffic_allocation?: number = 1
experiment_js?: string | null
experiment_reset_js?: string | null
experiment_css?: string | null
sequential_testing_enabled?: boolean
significance_level?: number
statistical_power?: number
store_url?: string (uri) | null
auto_stop_override?: AutoStopOverride
targeting?: ExperimentTargeting | null
variations: object[]
primary_goal_id?: string (uuid) | null
goal_ids?: string (uuid)[]
expectedCommerceRevision?: integer | null
expectedArtifactHash?: string
definition?: object
}Responses
200Experiment updated201Experiment created400Validation error401Unauthorized409Conflict (experiment not in draft status or slug already exists). Retired historical targeting writes return 409 checkout_mode_retired. commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422Invalid experiment constraints. commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.429Rate limit exceeded503
curl -X PUT \
https://app.drip-apex.com/api/v1/experiments/by-slug/example_slug \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "example_name",
"variations": []
}'Attach an idea to an experiment
/experiments/{id}/idea
Links the experiment to one idea. Send exactly one of backlog_item_id or idea_key. Repeating the request returns the existing link with 200 and writes no audit entry.
Parameters
idin path required
Request body
{
backlog_item_id?: string (uuid)
idea_key?: string
as_follow_up?: boolean = false
}Responses
200The experiment was already linked to this idea201Idea attached400No identifier, or a field of the wrong type401Unauthorized403404Experiment or idea not found409The experiment already carries another idea, the idea already has a primary test, or a follow-up was requested for an idea with no primary test. error.code names which one; the dashboard route reports the same codes.422Both backlog_item_id and idea_key were sent429Rate limit exceeded503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/idea \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"backlog_item_id": "uuid-here",
"idea_key": "example_idea_key",
"as_follow_up": false
}'Detach the linked idea from an experiment
/experiments/{id}/idea
Removes one idea link. Without an identifier the primary link goes first, then the oldest link.
Parameters
idin path requiredbacklog_item_idin query - Remove the link to this idea instead of the primary link.idea_keyin query - Shop backlog key alternative to backlog_item_id.
Responses
200Idea detached401Unauthorized403404Experiment or idea link not found422Both backlog_item_id and idea_key were sent, or an identifier was repeated or blank429Rate limit exceeded503
curl -X DELETE \
https://app.drip-apex.com/api/v1/experiments/exp_123/idea \
-H "Authorization: Bearer <apex-secret-key>"Pause experiment
/experiments/{id}/pause
Pauses a running experiment.
Parameters
idin path requiredscopein query - Set to store to act only on this store; omitted acts on every store when this is a brand-test parent.
Responses
200Experiment paused400Only running experiments can be paused404Experiment not found502Commerce configuration failed before commit, or the lifecycle change committed but storefront publication failed503Publication lock contended; retry after the Retry-After interval
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/pause \
-H "Authorization: Bearer <apex-secret-key>"Complete experiment
/experiments/{id}/complete
Marks a running or paused experiment as completed. Sets ended_at timestamp.
Parameters
idin path requiredscopein query - Set to store to act only on this store; omitted acts on every store when this is a brand-test parent.
Responses
200Experiment completed400Only running or paused experiments can be completed404Experiment not found502Commerce configuration failed before commit, or the lifecycle change committed but storefront publication failed503Publication lock contended; retry after the Retry-After interval
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/complete \
-H "Authorization: Bearer <apex-secret-key>"Duplicate experiment
/experiments/{id}/duplicate
Creates a faithful copy of the experiment in draft status with its configuration, variation artifacts, landing-page bindings, and goal associations. Review, scheduling, runtime, and result state do not carry over.
Parameters
idin path required
Request body
{
expectedSourceCommerceRevision?: integer | null
expectedSourceArtifactHashes?: object
}Responses
201Duplicate experiment created: the serialized v1 experiment with canonical targeting.url and targeting.authorUrl rule arrays.400A variation artifact could not be copied; no duplicate is created404Experiment not found409A concurrent write caused a temporary transaction conflict (error code tx_write_conflict); retry the request. commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/duplicate \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expectedSourceCommerceRevision": 1,
"expectedSourceArtifactHashes": {}
}'Generate experiment with AI
/experiments/generate
Uses AI to generate an experiment configuration based on a store URL and prompt.
Request body
{
store_url: string (uri)
prompt: string
primary_goal_id?: string (uuid) | null
goal_ids?: string (uuid)[]
traffic_allocation?: number = 1
targeting?: ExperimentTargeting | null
screenshot_devices?: string[]
expectedCommerceRevision?: integer | null
expectedArtifactHash?: string
definition?: object
}Responses
201AI-generated experiment created400Validation error409commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/generate \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"store_url": "https://example.com",
"prompt": "example_prompt"
}'Replay experiment lifecycle events
/experiment-events
Returns sequenced events for the API key's shop. Positions are gap-tolerant opaque decimal cursors that strictly increase in exposure order. When no further event matches, next_after jumps to the newest committed position so filtered polls do not rescan.
Parameters
afterin querylimitin querytypesin query - Comma-separated event types
Responses
200An ordered page of experiment lifecycle events
curl \
https://app.drip-apex.com/api/v1/experiment-events \
-H "Authorization: Bearer <apex-secret-key>"Read the stored commerce definition
/experiments/{id}/commerce
Parameters
idin path required - Experiment UUID or public ID / test key
Responses
200Canonical definition or stored legacy envelope, with compile output401403404Experiment not found in this API key's shop, or it has no commerce definition429503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/commerce \
-H "Authorization: Bearer <apex-secret-key>"Compile and save the canonical commerce definition
/experiments/{id}/commerce
Parameters
idin path required - Experiment UUID or public ID / test key
Request body
{
definition: CommerceDefinition
expectedRevision: integer | null
}Responses
200Definition saved400401403404Experiment not found in this API key's shop409commerce_definition_revision_conflict, commerce_definition_legacy_readonly, commerce_definition_running_price_locked, artifact_status_conflict (a completed test, or a running or paused shipping test), artifact_hash_conflict, experiment_archived, variation_archived, variation_not_found, or commerce_price_variant_relationship. Stale or changed generic save inputs, including missing, forged, or expired prepared evidence, yield commerce_definition_revision_conflict with error.details.retriable=true and no violations, whether or not a priceFloor exists. Carrier refusals list every blocking issue in error.details.blockingIssues. Floor-specific refusals use commerce_price_below_floor for evaluation or unavailable evidence: retriable true for unreadable costs when a margin is enforced or belowCost is block (even without a margin and under either missingCost policy), or solely unavailable target membership (truthful placeholder violations), false for any definitive violation including undefined known-cost margin at conservative bound zero. The shared refusal schema also permits the launch diagnostic for unreadable saved-definition JSON with the same code, empty violations and retriable true; this does not make malformed PUT bodies valid or identify an evaluated below-floor variant. Acknowledgement cannot bypass either refusal. Floor lives only in definition; defaults missingCost=block, belowCost=warn apply at evaluation when omitted, with no thresholds.422Compile refusals: commerce_definition_feature_unavailable, commerce_definition_price_invalid, commerce_definition_price_variants_unresolved, commerce_definition_variations_mismatch, commerce_definition_shipping_scopes_invalid (one to ten disjoint scopes and at most one default), commerce_definition_shipping_market_unresolved (market must resolve completely to active region countries), commerce_definition_shipping_rules_invalid (shipping rules need strictly ascending subtotals and free shipping only as the last rule; up to five rules per scope). Numeric and GID variant aliases resolve to the same variant; duplicate canonical overrides or scope ids refuse with commerce_definition_price_invalid. Unknown override or scope variants refuse with commerce_definition_price_variants_unresolved naming the variant. Inactive variants are listed in serverOwned.excludedVariants and missing included products in serverOwned.excludedProducts with reason not_found. Selection resolution refuses empty_selection, invalid_product_id, invalid_collection_id, installation_unavailable, catalog_scopes_missing, catalog_page_read_failed, catalog_page_incomplete, variant_page_incomplete, collection_membership_unresolved, variant_relationships_unresolved, invalid_variant_money, currency_mismatch, selection_resolution_failed, or a selection that prices no variants. commerce_definition_feature_unavailable also refuses selections over the 100-variant cap until #9033. When the artifact gate refuses a derived carrier, error.code is its first blocking code and error.details.blockingIssues lists all blocking issues.429503
curl -X PUT \
https://app.drip-apex.com/api/v1/experiments/exp_123/commerce \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expectedRevision": 1
}'Export effective selection prices as CSV
/experiments/{id}/commerce/price/csv
Parameters
idin path required
Responses
200JSON envelope with RFC 4180 CSV text, definition revision, and text/csv; charset=utf-8 contentType. Informational columns product_id,variant_id,sku,product_title,variant_title,base_price followed by editable price version slugs in versions.order. Read-only candidate price columns follow editable columns. Informational <slug>__floor_status columns follow all price columns in versions.order and are ignored on import. Read-only percentage price cells are blank without an authored price and make no selling-price claim. Exact amount-off and authored prices remain unchanged. Failed cost reads retain missing_cost diagnostics.401403404Experiment or definition not found in this shop; commerce_price_table_unavailable when there is no sold-item pricing projection409commerce_price_table_unavailable for legacy or unresolved definitions422commerce_definition_price_invalid, commerce_definition_price_variants_unresolved429500Unexpected server error503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/commerce/price/csv \
-H "Authorization: Bearer <apex-secret-key>"Import price overrides from CSV
/experiments/{id}/commerce/price/csv
Parameters
idin path requiredIf-Matchin header required - Quoted expected definition revision
Responses
200Import result; unchanged definitions are not written400commerce_price_csv_revision_invalid: If-Match is not a quoted positive integer401403404Experiment or definition not found in this shop; commerce_price_table_unavailable when there is no sold-item pricing projection409commerce_definition_revision_conflict, commerce_definition_legacy_readonly, commerce_definition_running_price_locked, artifact_status_conflict (a completed test, or a running or paused shipping test), artifact_hash_conflict, experiment_archived, variation_archived, variation_not_found, or commerce_price_variant_relationship. Stale or changed generic save inputs, including missing, forged, or expired prepared evidence, yield commerce_definition_revision_conflict with error.details.retriable=true and no violations, whether or not a priceFloor exists. Carrier refusals list every blocking issue in error.details.blockingIssues. Floor-specific refusals use commerce_price_below_floor for evaluation or unavailable evidence: retriable true for unreadable costs when a margin is enforced or belowCost is block (even without a margin and under either missingCost policy), or solely unavailable target membership (truthful placeholder violations), false for any definitive violation including undefined known-cost margin at conservative bound zero. The shared refusal schema also permits the launch diagnostic for unreadable saved-definition JSON with the same code, empty violations and retriable true; this does not make malformed PUT bodies valid or identify an evaluated below-floor variant. Acknowledgement cannot bypass either refusal. Floor lives only in definition; defaults missingCost=block, belowCost=warn apply at evaluation when omitted, with no thresholds.; commerce_price_table_unavailable413commerce_price_csv_too_large: body exceeds 2 MB415commerce_price_csv_content_type_invalid: Content-Type must be text/csv422Compile refusals: commerce_definition_feature_unavailable, commerce_definition_price_invalid, commerce_definition_price_variants_unresolved, commerce_definition_variations_mismatch, commerce_definition_shipping_scopes_invalid (one to ten disjoint scopes and at most one default), commerce_definition_shipping_market_unresolved (market must resolve completely to active region countries), commerce_definition_shipping_rules_invalid (shipping rules need strictly ascending subtotals and free shipping only as the last rule; up to five rules per scope). Numeric and GID variant aliases resolve to the same variant; duplicate canonical overrides or scope ids refuse with commerce_definition_price_invalid. Unknown override or scope variants refuse with commerce_definition_price_variants_unresolved naming the variant. Inactive variants are listed in serverOwned.excludedVariants and missing included products in serverOwned.excludedProducts with reason not_found. Selection resolution refuses empty_selection, invalid_product_id, invalid_collection_id, installation_unavailable, catalog_scopes_missing, catalog_page_read_failed, catalog_page_incomplete, variant_page_incomplete, collection_membership_unresolved, variant_relationships_unresolved, invalid_variant_money, currency_mismatch, selection_resolution_failed, or a selection that prices no variants. commerce_definition_feature_unavailable also refuses selections over the 100-variant cap until #9033. When the artifact gate refuses a derived carrier, error.code is its first blocking code and error.details.blockingIssues lists all blocking issues.; commerce_price_csv_header_mismatch, commerce_price_csv_unknown_variant, commerce_price_csv_duplicate_variant, commerce_price_csv_invalid. Invalid money uses commerce_definition_price_invalid with error.details.line and column. Unknown variants carry line.428commerce_price_csv_revision_required: If-Match is required429500Unexpected server error503
curl -X PUT \
https://app.drip-apex.com/api/v1/experiments/exp_123/commerce/price/csv \
-H "Authorization: Bearer <apex-secret-key>"Read grouped effective price rows
/experiments/{id}/commerce/price/rows
Parameters
idin path requiredgroupin querylimitin query - Number of groupscursorin query - Opaque cursor from the preceding page
Responses
200Groups sorted by label then key. Collection grouping includes only selected collections. Read-only amount-off and offer cells include discount sources. Every evaluated cell includes floor diagnostics before pagination; failed cost reads retain exact prices and missing_cost reasons, without inventing verified costs.400commerce_price_rows_query_invalid: invalid grouping, limit or cursor401403404Experiment or definition not found in this shop; commerce_price_table_unavailable when there is no sold-item pricing projection409commerce_price_table_unavailable for legacy or unresolved definitions422commerce_definition_price_invalid, commerce_definition_price_variants_unresolved429500Unexpected server error503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/commerce/price/rows \
-H "Authorization: Bearer <apex-secret-key>"Read experiment launch readiness
/experiments/{id}/readiness
Read-only checklist and human launch handoff. Requires experiments:read on shop keys or shops:read on user keys. Does not create QA runs, approve QA, or launch. Screenshot URLs are signed from the latest persisted QA run. The checkout item reports checkout-message warnings and live confirmation for launchable or running checkout tests.
Parameters
idin path required - Experiment UUID or public IDvariation_idin query - Select a live variation by UUID, public ID or slug for the signed preview.target_urlin query - Signed preview destination on the experiment's allowed store origin.
Responses
200Readiness checklist401Invalid or missing API key403Read scope or shop access required404Experiment not found in this shop429Rate limit exceeded503Rate-limit backend unavailable
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/readiness \
-H "Authorization: Bearer <apex-secret-key>"Read return on ad spend per experiment variant
/experiments/{id}/results/ad-roas
Current-run Meta ad-arrived visitors and shop-currency matched revenue, with spend allocated by each ad-hour's share of all matching traffic. Variant spend reconciles within a test; overlapping tests are not additive. Revenue per ad visitor includes non-buyers as zeros, with Welch intervals and differences from original. ROAS has no significance claim and is never a decision basis. Missing FX, truncation and read failures never become measured zeros.
Parameters
idin path required
Responses
200Ad ROAS read-out or explicit unknown state401403404429503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/results/ad-roas \
-H "Authorization: Bearer <apex-secret-key>"Read experiment money by arrival channel
/experiments/{id}/results/by-channel
Current-run snapshot through as_of, pinned to a published copy generation. Identified money uses all exposed visitors, including non-buyers as zeros. Matched money plus identified unmatched and identity-free residuals equals the headline recomputed through that cutoff. Unavailable and degraded never carry zero-filled money. Channel comparisons suggest follow-up tests, never winners. Unknown includes missing consent and pre-tracking exposures. Updates hourly.
Parameters
idin path required
Responses
200Snapshot or explicit unavailable/degraded state401403404429503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/results/by-channel \
-H "Authorization: Bearer <apex-secret-key>"Admin
Create a personal managed-client join link
/admin/client-links
Requires a user API key with admin:write. Uses the same validation, sellable-plan, consultant, referral and conflict rules as Admin > Links. It creates the link but does not send email.
Request body
{
kind: "invite"
plan: "self_managed" | "apex_managed" | "drip_managed"
client_name: string
client_type: "company" | "agency"
email: string (email)
consultant_user_id?: string (uuid) | null
consultant_title?: string | null
payment?: "card" | "invoice"
discount_pct?: integer | null
discount_months?: integer | null
free_days?: integer
store_url?: string (uri) | null
expires_at?: string | null
internal_note?: string | null
teammates?: object[]
}Responses
201Created link and join URL400401403409429503
curl -X POST \
https://app.drip-apex.com/api/v1/admin/client-links \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"kind": "invite",
"plan": "self_managed",
"client_name": "example_client_name",
"client_type": "company",
"email": "example_email"
}'Create an engineering account
/admin/customers
Requires a user API key with admin:write and a live platform role with onboarding:write. Customer accounts are refused with use_client_link; create them with POST /api/v1/admin/client-links (apex admin clients create) or Admin, Links. A claimed store also requires admin:write. Every creation records the reason in the audit.
Request body
{
name: string
type: "company" | "agency"
category: "internal" | "demo" | "dogfood"
plan: "self_managed" | "apex_managed" | "drip_managed"
store?: any
ownerEmail?: string (email)
teammates: object[]
consultant?: any
reason: string
}Responses
201Created, including any invitation delivery failures400Invalid request, consultant_required or use_client_link401Missing, expired or revoked user API key402First store requires checkout; no account retained403Missing key scope or live platform permission409Name, domain, store claim or owner conflict429Rate limit exceeded503Audit, provisioning or owner identity verification unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/admin/customers \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "example_name",
"type": "company",
"category": "internal",
"plan": "self_managed",
"teammates": [],
"reason": "example_reason"
}'Change an account category, managing agency or workspace type
/admin/customers/{orgId}
Requires a user API key with admin:write. Moves an existing organization between the customer, internal, demo and dogfood categories through the audited customer change path; the reason is recorded in the audit. Managing agency and workspace type changes require platform admin access. Only active, unmanaged agency organizations can manage a customer; self-management and chains are refused. Workspace type changes refuse archived organizations and unchanged values; becoming an agency requires no active stores or managing agency, and becoming a company requires unlinking every managed organization. A competing customer change answers 409.
Request body
anyResponses
200Category, managing agency or workspace type changed400Invalid organization id, category, workspace type or reason; a managed organization cannot become an agency (managed)401Missing, expired or revoked user API key403Missing key scope or live platform permission404Customer not found409Customer changed before the write; refresh and retry. Workspace type conflicts: unchanged, stores_present, manages_organizations or archived429Rate limit exceeded503Audit could not be recorded
curl -X PATCH \
https://app.drip-apex.com/api/v1/admin/customers/org_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Preview or move a set of stores to a customer
/admin/customers/{orgId}/move-stores
Requires admin:write on a user API key and the live apex-platform-admin role. dry_run returns the before/after access plan without moving stores or writing move audits. Confirmation uses the audited store-set chokepoint; keep_grants preserves explicit roles and overrides.
Request body
{
shop_ids: string (uuid)[]
reason: string
keep_grants?: boolean = false
dry_run?: boolean = false
}Responses
200Plan or completed move: shops, brands, scope counts, programs, entitlement change, kept/removed grants and auditIds400Invalid store ids or reason401Invalid user API key403Platform admin required404Store or destination not found409Brand split or concurrent move conflict429Rate limit exceeded503Audit unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/admin/customers/org_123/move-stores \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_ids": [],
"reason": "example_reason",
"keep_grants": false,
"dry_run": false
}'Read a customer's setup checklist
/admin/customers/{orgId}/setup
Requires admin:read on a user API key. Each item says whether it is done, open, waiting on the client, optional or unknown, and names its fix. A Research Hub read failure is reported in degraded, never as done (#9232).
Responses
200Setup checklist400Invalid customer id401Invalid user API key403admin:read required404Customer not found429Rate limit exceeded503Database busy
curl \
https://app.drip-apex.com/api/v1/admin/customers/org_123/setup \
-H "Authorization: Bearer <apex-secret-key>"Apply one setup checklist fix
/admin/customers/{orgId}/setup
Requires admin:write on a user API key. Runs the existing audited writer for the fix with that writer's own permission check, then returns the fresh checklist (#9232).
Request body
{
fix: "rename" | "set_plan" | "set_managed_by" | "set_consultant" | "enable_switch" | "resend_owner_invite" | "invite_owner" | "retry_teammates" | "link_research_hub" | "apply_plan_switches"
shopId?: string (uuid)
input?: any
reason: string
}Responses
200Updated setup checklist400Invalid fix, input or reason401Invalid user API key403Permission for this fix required404Customer, store or invitation not found409The fix conflicts with the current state (owner_exists, owner_invitation_pending, research_hub_already_linked, research_hub_link_changed)429Rate limit exceeded503Audit unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/admin/customers/org_123/setup \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"fix": "rename",
"shopId": "uuid-here",
"reason": "example_reason"
}'Read an organization’s monthly unique visitors
/admin/customers/{orgId}/unique-visitors
Each consented web visitor ID that loads at least one page with an active Apex script counts once per shop and calendar month in the shop’s analytics timezone (report timezone, then Shopify installation timezone, then UTC); detected bots, crawlers, preview, QA, non-production traffic and anonymous sentinel identities are excluded. Platform user key with admin:read scope and platform billing:read permission required. Includes archived shops, ordered by name. Failed shop reads have null counts and a null organization total. A month that has not started or is past the 365-day retention answers 400; a shop whose own timezone has not reached the month reads month_not_counted.
Parameters
orgIdin path requiredmonthin query required
Responses
200All organization shops for one month400Invalid month or range401403404Organization not found429503Analytics or shop timezone temporarily unavailable; never reported as zero
curl \
https://app.drip-apex.com/api/v1/admin/customers/org_123/unique-visitors \
-H "Authorization: Bearer <apex-secret-key>"Platform admin
Archive a shop
/admin/shops/{id}/archive
Requires an admin:write user API key and refuses shops with running, non-archived experiments.
Responses
200Shop archived and delivery disabled401403404Shop not found (pass-through orchestration body)409Shop has running, non-archived experiments429503Archival persisted but snippet republication pending, or publication lock contention — retriable
curl -X POST \
https://app.drip-apex.com/api/v1/admin/shops/exp_123/archive \
-H "Authorization: Bearer <apex-secret-key>"Read every registered per-shop setting
/admin/shops/{id}/settings
Requires an admin:read platform user API key. Exact domain matching is case-insensitive; archived shops remain resolvable. Returns registry metadata and nullable override state for every registered key.
Responses
200Shop identity and registered settings401403404Shop not found409Ambiguous domain; pass a shop UUID429503
curl \
https://app.drip-apex.com/api/v1/admin/shops/exp_123/settings \
-H "Authorization: Bearer <apex-secret-key>"Write a registered per-shop setting
/admin/shops/{id}/settings
Requires an admin:write platform user API key plus the registry's per-key adminWrite permission. Uses the audited shop-settings writer; keys owned by other write paths are refused. Resolve a domain with GET first, then pin the resolved UUID with x-apex-shop. Does not return a settings snapshot.
Parameters
x-apex-shopin header required - Must equal the resolved shop UUID, including when id is a domain
Request body
{
key: string
value: boolean | string | number | null
reason?: string
expectedVersion?: string | null
}Responses
200Audited write persisted; precompile reports publication outcome when applicable400Invalid payload or missing/mismatched x-apex-shop pin; validation issues appear at error.details.issues401403Platform permission or registry per-key write permission refused404Shop not found409Stale expectedVersion or ambiguous domain (error.details.candidates lists shop UUIDs); use a shop UUID429503Required audit could not be recorded or database temporarily unavailable
curl -X PATCH \
https://app.drip-apex.com/api/v1/admin/shops/exp_123/settings \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"key": "example_key",
"value": false,
"reason": "example_reason",
"expectedVersion": "example_expectedVersion"
}'Restore an archived shop
/admin/shops/{id}/restore
Requires an admin:write user API key. Delivery remains controlled by the shop runtime kill switch.
Responses
200Shop restored401403404Shop not found (pass-through orchestration body)429503
curl -X POST \
https://app.drip-apex.com/api/v1/admin/shops/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"List tracking quarantine events
/admin/tracking/quarantine
Read-only platform view requiring a live PlatformAccess row.
Parameters
shopIdin queryreasonCodein queryverdictin querylimitin query
Responses
200Quarantine rows401403429503
curl \
https://app.drip-apex.com/api/v1/admin/tracking/quarantine \
-H "Authorization: Bearer <apex-secret-key>"Read staff tracking health
/admin/shops/{id}/tracking-health
Returns tracking health for one shop with staff-only causes for unlinked orders.
Responses
200Staff per-shop tracking health401403404429503
curl \
https://app.drip-apex.com/api/v1/admin/shops/exp_123/tracking-health \
-H "Authorization: Bearer <apex-secret-key>"Account
Get the current user's profile
/account/profile
Responses
200Account profile401403429503
curl \
https://app.drip-apex.com/api/v1/account/profile \
-H "Authorization: Bearer <apex-secret-key>"Update the current user's profile
/account/profile
Request body
{
displayName?: string
avatarDataUrl?: string | null
themePreference?: "system" | "dark" | "light"
}Responses
200Updated account profile400401403429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/account/profile \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"displayName": "example_displayName",
"avatarDataUrl": "example_avatarDataUrl",
"themePreference": "system"
}'Keys
Read the presented key's shop identity
/key
Any valid shop key can read its own identity without a resource scope. Shop context headers and query parameters do not change its shop. User keys return kind user and shop_id null without resolving shop context. MCP user credentials continue to resolve their bound shop through /keys/self/shop.
Responses
200Key identity401403Key or shop access is denied429503Rate limiting backend unavailable
curl \
https://app.drip-apex.com/api/v1/key \
-H "Authorization: Bearer <apex-secret-key>"Read the connector's bound shop
/keys/self/shop
User keys only, shops:write. Returns the existing bound shop's domain and platform without requiring shops:read. Organization and live shop authorization are checked; unbound keys return 400.
Responses
200Connector's bound shop400401403404429503
curl \
https://app.drip-apex.com/api/v1/keys/self/shop \
-H "Authorization: Bearer <apex-secret-key>"Bind the connector to a shop
/keys/self/shop
User keys only, shops:write. Bind an unbound key once to an authorized shop in its organization; a concurrent or subsequent binding returns 409. Binding and required audit are atomic.
Request body
{
shop_id: string (uuid)
}Responses
200Connector bound400401403404409429503
curl -X POST \
https://app.drip-apex.com/api/v1/keys/self/shop \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "uuid-here"
}'API keys
List a shop's API keys
/keys
Parameters
shop_idin query required
Responses
200API key metadata; hashes and secrets are never included400403404429503
curl \
https://app.drip-apex.com/api/v1/keys \
-H "Authorization: Bearer <apex-secret-key>"Create a shop API key
/keys
Request body
{
shop_id: string (uuid)
name: string
type: "live_secret" | "test_secret" | "publishable"
permissions?: string[]
rate_limit?: integer
expires_at?: string (date-time) | null
}Responses
201Created key, including its secret exactly once400403404429503
curl -X POST \
https://app.drip-apex.com/api/v1/keys \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "uuid-here",
"name": "example_name",
"type": "live_secret"
}'Revoke a shop API key
/keys/{id}
Parameters
idin path requiredshop_idin query required
Responses
200Revoked400403404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/keys/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"External tracking
Read cross-domain identity handover settings
/external-tracking
Responses
200External tracking settings401403429503
curl \
https://app.drip-apex.com/api/v1/external-tracking \
-H "Authorization: Bearer <apex-secret-key>"Update cross-domain identity handover settings
/external-tracking
Request body
{
externalTracking?: object
ifRevision?: string (date-time) | null
}Responses
200Updated handover settings and edge precompile outcome400401403409429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/external-tracking \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"externalTracking": {},
"ifRevision": "example_ifRevision"
}'Variations
Generate variation with AI
/experiments/{id}/variations/generate
Uses AI to generate a new variation for an existing experiment.
Parameters
idin path required - Experiment ID
Request body
{
prompt: string
expectedCommerceRevision?: integer | null
expectedArtifactHash?: string
definition?: object
}Responses
201AI-generated variation created400Validation error404Experiment not found409commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/generate \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"prompt": "example_prompt",
"expectedCommerceRevision": 1,
"expectedArtifactHash": "example_expectedArtifactHash",
"definition": {}
}'Create variation
/variations
Creates a new variation for a draft experiment. Requires experiment_id in the body.
Request body
{
experiment_id: string (uuid) | string
name: string
weight: number
is_control?: boolean = false
redirect_url?: string (uri) | null
mutations?: Mutation[]
url_blocks?: UrlBlocks
custom_css?: string | null
custom_js?: string | null
expectedCommerceRevision?: integer | null
}Responses
201Variation created400Validation error or experiment not in draft status404Experiment not found409commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.
curl -X POST \
https://app.drip-apex.com/api/v1/variations \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "example_name",
"weight": 1
}'Get variation
/variations/{id}
Returns a single variation by ID.
Parameters
idin path required
Responses
200Variation details403Not authorized to access this variation404Variation not found
curl \
https://app.drip-apex.com/api/v1/variations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update variation
/variations/{id}
Partially updates a variation. Treatment artifacts (mutations, URL blocks, and custom CSS/JavaScript) can change while the experiment is draft, paused, or running. Running artifact writes republish the storefront configuration. Other variation fields remain draft-only. Cannot update is_control.
Parameters
idin path required
Request body
{
name?: string
weight?: number
redirect_url?: string (uri) | null
expected_prior_redirect_url?: string (uri) | null
mutations?: Mutation[]
url_blocks?: UrlBlocks
custom_css?: string | null
custom_js?: string | null
expectedCommerceRevision?: integer | null
expectedArtifactHash?: string
definition?: object
}Responses
200Variation updated400The write was rejected, or treatment artifacts committed before a later publication step reported a failure403Not authorized404Variation not found409Treatment artifacts cannot change after the experiment finishes, the redirect destination changed since it was read, the experiment is no longer editable, or a price-test mutation targets a subscription-priced or bundle-component variant commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.502The running treatment artifact was saved, but storefront publication failed
curl -X PATCH \
https://app.drip-apex.com/api/v1/variations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive variation
/variations/{id}
Archives a non-control variation from a draft experiment. Minimum 2 active variations must remain, and remaining active weights are redistributed.
Parameters
idin path required
Request body
{
expectedCommerceRevision?: integer | null
}Responses
200Variation archived400Cannot delete control, non-draft experiment, or last two variations403Not authorized404Variation not found409commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.
curl -X DELETE \
https://app.drip-apex.com/api/v1/variations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expectedCommerceRevision": 1
}'Restore variation
/variations/{id}/restore
Restores an archived variation and redistributes all active variation weights.
Parameters
idin path required
Request body
{
expectedCommerceRevision?: integer | null
}Responses
200Variation restored404Variation not found409Variation is already active or changed concurrently commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.
curl -X POST \
https://app.drip-apex.com/api/v1/variations/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expectedCommerceRevision": 1
}'Apex Workspace
Read variation workspace
/experiments/{id}/variations/{variationId}/workspace
Returns the canonical Apex workspace for an exact experiment variation. Used by `apex workspace checkout`.
Parameters
idin path required - Experiment IDvariationIdin path required - Variation ID
Responses
200Canonical workspace payload401Unauthorized403Not authorized to access this variation404Experiment variation workspace not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/variation_123/workspace \
-H "Authorization: Bearer <apex-secret-key>"List variation workspace files
/experiments/{id}/variations/{variationId}/files
Lists canonical workspace files for an exact experiment variation.
Parameters
idin path requiredvariationIdin path required
Responses
200Workspace files401Unauthorized403Not authorized to access this variation404Experiment variation workspace not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/variation_123/files \
-H "Authorization: Bearer <apex-secret-key>"Read variation workspace file
/experiments/{id}/variations/{variationId}/files/{path}
Reads one canonical workspace file. URL-encode the file path, for example src%2Findex.js.
Parameters
idin path requiredvariationIdin path requiredpathin path required - Canonical workspace path, URL-encoded. Supported roots: src, goals, segments, network, metrics.json, targeting.json, experiment.config.json, dev-doc.md.
Responses
200Workspace file401Unauthorized403Not authorized to access this variation404Workspace file not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/variation_123/files/example_path \
-H "Authorization: Bearer <apex-secret-key>"Write variation workspace file
/experiments/{id}/variations/{variationId}/files/{path}
Writes one canonical workspace file and records a workspace revision. Supply expected_version from the current file sha256 to reject a concurrent edit.
Parameters
idin path requiredvariationIdin path requiredpathin path required - Canonical workspace path, URL-encoded. Supported roots: src, goals, segments, network, metrics.json, targeting.json, experiment.config.json, dev-doc.md.
Request body
{
content: string
source?: "manual" | "operator" | "build" | "system" = "operator"
title?: string
expected_version?: string
}Responses
200Workspace file write result400Validation error or unsupported workspace path401Unauthorized403Not authorized to edit this variation404Experiment variation workspace not found409The workspace file changed or no longer exists429Rate limit exceeded503
curl -X PUT \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/variation_123/files/example_path \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"content": "example_content",
"source": "manual",
"title": "example_title",
"expected_version": "example_expected_version"
}'Build variation workspace
/experiments/{id}/variations/{variationId}/build
Projects canonical workspace files into Apex runtime state: mutations, CSS, JS, targeting, goals, and segments.
Parameters
idin path requiredvariationIdin path required
Request body
{
mode?: "full" | "validate_only" = "full"
allowGoalWrites?: boolean = false
expectedCommerceRevision?: integer | null
expectedArtifactHash?: string
definition?: object
expectedWorkspaceRevision?: string
expectedFileVersions?: object
}Responses
200Workspace build succeeded400Workspace build failed401Unauthorized403Not authorized to edit this variation404Experiment variation workspace not found409Workspace build conflicted with a concurrent artifact save commerce_definition_revision_conflict, artifact_hash_conflict, commerce_definition_legacy_readonly, or workspace/file conflict. No automatic retry with fresh tokens.422commerce_definition_feature_unavailable with escaped paths; no partial authoring writes.429Rate limit exceeded503
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/variation_123/build \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'List variation workspace revisions
/experiments/{id}/variations/{variationId}/revisions
Lists recent workspace revisions for an exact experiment variation.
Parameters
idin path requiredvariationIdin path required
Responses
200Workspace revisions401Unauthorized403Not authorized to access this variation404Experiment variation workspace not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/experiments/exp_123/variations/variation_123/revisions \
-H "Authorization: Bearer <apex-secret-key>"Targeting
Test URL targeting
/targeting/test
Evaluates the API key shop's experiment targeting rules against one URL and returns all matching experiments for parallel-experiment awareness.
Parameters
urlin query requiredstatusin query - Comma-separated statuses or all. Defaults to draft,qa,running,paused.
Responses
200Targeting evaluation400Invalid URL401Unauthorized403API key is missing experiment read permission429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/targeting/test \
-H "Authorization: Bearer <apex-secret-key>"Harness
Publish harness session
/harness/sessions/{id}/publish
Publishes reviewed harness artifacts to the session variation.
Parameters
idin path required
Responses
200Harness session published400Missing JavaScript artifact or artifact review blocked publish404Harness session not found409Artifact conflict or experiment is not in draft status
curl -X POST \
https://app.drip-apex.com/api/v1/harness/sessions/exp_123/publish \
-H "Authorization: Bearer <apex-secret-key>"Screenshots
Create screenshot job
/experiments/{id}/screenshots
Queues a screenshot capture job for the specified variations and devices.
Parameters
idin path required - Experiment ID
Request body
{
devices: string[]
variation_ids: (string (uuid) | string)[]
}Responses
202Screenshot job queued400Validation error404Experiment not found
curl -X POST \
https://app.drip-apex.com/api/v1/experiments/exp_123/screenshots \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"devices": [],
"variation_ids": []
}'Get screenshot job status
/screenshots/{jobId}
Returns the current status and results of a screenshot job.
Parameters
jobIdin path required
Responses
200Screenshot job details404Job not found
curl \
https://app.drip-apex.com/api/v1/screenshots/job_123 \
-H "Authorization: Bearer <apex-secret-key>"Webhooks
List webhooks
/webhooks
Returns all webhooks for the shop.
Parameters
include_archivedin query - Include archived webhooks.
Responses
200List of webhooks401Unauthorized
curl \
https://app.drip-apex.com/api/v1/webhooks \
-H "Authorization: Bearer <apex-secret-key>"Create webhook
/webhooks
Registers a new webhook endpoint. URL must use HTTPS. Events include experiment.created, experiment.status_changed, experiment.dates_changed, experiment.targeting_changed, experiment.decided, experiment.results_snapshot, experiment.qa_signed_off, experiment.client_approval, experiment.ticket_linked, experiment.design_finished, experiment.plan_linked, experiment.archived, and the existing lifecycle and project events.
Request body
{
url: string (uri)
events: ("experiment.created" | "experiment.status_changed" | "experiment.dates_changed" | "experiment.targeting_changed" | "experiment.decided" | "experiment.results_snapshot" | "experiment.qa_signed_off" | "experiment.client_approval" | "experiment.ticket_linked" | "experiment.design_finished" | "experiment.plan_linked" | "experiment.archived" | "experiment.started" | "experiment.completed" | "experiment.significant" | "experiment.guardian_triggered" | "experiment.guardian_paused" | "variation.winning" | "screenshots.completed" | "project.published")[]
}Responses
201Webhook created400Validation error
curl -X POST \
https://app.drip-apex.com/api/v1/webhooks \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"events": []
}'Get webhook
/webhooks/{id}
Parameters
idin path required
Responses
200Webhook404Webhook not found
curl \
https://app.drip-apex.com/api/v1/webhooks/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update webhook
/webhooks/{id}
Parameters
idin path required
Request body
{
url?: string (uri)
events?: ("experiment.created" | "experiment.status_changed" | "experiment.dates_changed" | "experiment.targeting_changed" | "experiment.decided" | "experiment.results_snapshot" | "experiment.qa_signed_off" | "experiment.client_approval" | "experiment.ticket_linked" | "experiment.design_finished" | "experiment.plan_linked" | "experiment.archived" | "experiment.started" | "experiment.completed" | "experiment.significant" | "experiment.guardian_triggered" | "experiment.guardian_paused" | "variation.winning" | "screenshots.completed" | "project.published")[]
active?: boolean
}Responses
200Webhook400Invalid JSON, an unknown field, an unsupported event, or a URL that is malformed, not HTTPS, carries credentials or does not resolve403The API key lacks webhooks:write, or the URL targets a local, private or reserved address404Webhook not found409Webhook is archived; restore it before editing
curl -X PATCH \
https://app.drip-apex.com/api/v1/webhooks/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"events": [],
"active": false
}'Archive webhook
/webhooks/{id}
Archives a webhook registration while retaining delivery history.
Parameters
idin path required
Responses
200Webhook deleted404Webhook not found
curl -X DELETE \
https://app.drip-apex.com/api/v1/webhooks/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Restore webhook
/webhooks/{id}/restore
Parameters
idin path required
Responses
200Restored webhook404Webhook not found
curl -X POST \
https://app.drip-apex.com/api/v1/webhooks/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"List webhook deliveries
/webhooks/{id}/deliveries
Returns recent delivery attempts for one webhook. Delivery IDs are also sent as the X-Apex-Delivery-Id header on outbound webhook requests.
Parameters
idin path requiredstatusin querylimitin query
Responses
200List of webhook delivery attempts404Webhook not found
curl \
https://app.drip-apex.com/api/v1/webhooks/exp_123/deliveries \
-H "Authorization: Bearer <apex-secret-key>"Send test webhook
/webhooks/{id}/test
Sends a signed test event and records the delivery.
Parameters
idin path required
Responses
200Test delivery. HTTP 200 reports that the attempt was recorded; delivery.status says whether the endpoint accepted it.404Webhook not found409Webhook is archived or inactive
curl -X POST \
https://app.drip-apex.com/api/v1/webhooks/exp_123/test \
-H "Authorization: Bearer <apex-secret-key>"Organization
Get the credential organization
/org
Responses
200Dashboard organization detail shape, including a `members` array (id, userId, email, role, defaultShopRole, createdAt), `_count.members`, and the organization policy booleans `mfa_enforced` (authenticator MFA required for members) and `ai_eu_only` (AI inference restricted to EU zero-retention endpoints). Both policies are read-only here and change only in the dashboard General Settings.401403429503
curl \
https://app.drip-apex.com/api/v1/org \
-H "Authorization: Bearer <apex-secret-key>"Update organization settings
/org
Request body
{
name?: string
slug?: string
billing_email?: string | null
logo_url?: string | null
sso_enabled?: boolean
sso_enforced?: boolean
sso_domain?: string | null
sso_provider_id?: string | null
}Responses
200Updated organization401403429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/org \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'List organization members
/org/members
Responses
200Organization members, including suspension metadata400Organization context required401403Organization access denied or membership suspended404Organization not found429503
curl \
https://app.drip-apex.com/api/v1/org/members \
-H "Authorization: Bearer <apex-secret-key>"List organization invitations
/org/invitations
Responses
200Pending invitations401429503
curl \
https://app.drip-apex.com/api/v1/org/invitations \
-H "Authorization: Bearer <apex-secret-key>"Invite an organization member
/org/invitations
Request body
{
email: string (email)
org_role?: "admin" | "member" = "member"
default_shop_role?: "editor" = "editor"
}Responses
201Invitation created401429502The invitation email could not be delivered. The response message says whether the pending invitation was rolled back (safe to retry now) or is still present and blocking retries until it expires503
curl -X POST \
https://app.drip-apex.com/api/v1/org/invitations \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"email": "example_email",
"org_role": "admin",
"default_shop_role": "editor"
}'Revoke an organization invitation
/org/invitations/{id}
Parameters
idin path required
Responses
200Invitation revoked401404429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/org/invitations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update an organization member
/org/members/{id}
Parameters
idin path required
Request body
{
role?: "admin" | "member"
default_shop_role?: "editor"
suspended?: boolean
reason?: string
}Responses
200Updated member, including nullable suspension metadata400Invalid payload, self-suspension/reinstatement, or last active owner401403Insufficient permissions or membership suspended404Member not found429503The database is briefly busy (code database_busy, retry after the Retry-After seconds), or the suspension or reinstatement was saved but the access-cache refresh failed (code authz_invalidation_failed, repeat the same request to finish the refresh)
curl -X PATCH \
https://app.drip-apex.com/api/v1/org/members/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"role": "admin",
"default_shop_role": "editor",
"suspended": false,
"reason": "example_reason"
}'Remove an organization member
/org/members/{id}
Parameters
idin path required
Responses
200Member removed401429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/org/members/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Team
List shop team members
/team/members
Parameters
shop_idin query required
Responses
200Team members401404429503
curl \
https://app.drip-apex.com/api/v1/team/members \
-H "Authorization: Bearer <apex-secret-key>"List shop invitations
/team/invitations
Parameters
shop_idin query required
Responses
200Invitations401429503
curl \
https://app.drip-apex.com/api/v1/team/invitations \
-H "Authorization: Bearer <apex-secret-key>"Invite a shop member
/team/invitations
Request body
{
email: string (email)
role: "editor" | "client_viewer"
shop_id: string (uuid)
permission_overrides?: object
}Responses
201Invitation created400401403The key's user lacks members:invite, or sent permission_overrides without members:manage409An open invitation already exists for this address. code: role_mismatch_pending_invitation when its role differs from the requested role (cancel it first); code: invitation_conflict_retry when a concurrent change could not be resolved (retry).429502The invitation email could not be delivered. The response message says whether the pending invitation was rolled back (safe to retry now) or is still present and blocking retries until it expires503
curl -X POST \
https://app.drip-apex.com/api/v1/team/invitations \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"email": "example_email",
"role": "editor",
"shop_id": "uuid-here",
"permission_overrides": {}
}'Revoke a shop invitation
/team/invitations/{id}
Parameters
idin path required
Request body
{
shop_id: string (uuid)
}Responses
200Invitation revoked401429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/team/invitations/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "uuid-here"
}'Update a shop member
/team/members/{id}
Request body
{
shop_id: string (uuid)
role?: "editor" | "client_viewer"
permission_overrides?: object
}Responses
200Member updated401429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/team/members/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "uuid-here",
"role": "editor",
"permission_overrides": {}
}'Remove a shop member
/team/members/{id}
Request body
{
shop_id: string (uuid)
}Responses
200Member removed401429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/team/members/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "uuid-here"
}'Shops
List organization shops
/shops
Responses
200Shop list
curl \
https://app.drip-apex.com/api/v1/shops \
-H "Authorization: Bearer <apex-secret-key>"Create a shop
/shops
For owners with a completed or waived signup-offer redemption bound to an organization, shops are created in that billed organization regardless of the requested workspace target or organization.
Request body
{
name: string
domain: string
platform: "shopify" | "hydrogen" | "shopware" | "woocommerce" | "centra" | "webflow" | "custom"
plan?: "managed" | "self_serve" = "self_serve"
origin?: string | null
currency?: "AED" | "AFN" | "ALL" | "AMD" | "ANG" | "AOA" | "ARS" | "AUD" | "AWG" | "AZN" | "BAM" | "BBD" | "BDT" | "BGN" | "BHD" | "BIF" | "BMD" | "BND" | "BOB" | "BOV" | "BRL" | "BSD" | "BTN" | "BWP" | "BYN" | "BZD" | "CAD" | "CDF" | "CHE" | "CHF" | "CHW" | "CLF" | "CLP" | "CNY" | "COP" | "COU" | "CRC" | "CUP" | "CVE" | "CZK" | "DJF" | "DKK" | "DOP" | "DZD" | "EGP" | "ERN" | "ETB" | "EUR" | "FJD" | "FKP" | "GBP" | "GEL" | "GHS" | "GIP" | "GMD" | "GNF" | "GTQ" | "GYD" | "HKD" | "HNL" | "HRK" | "HTG" | "HUF" | "IDR" | "ILS" | "INR" | "IQD" | "IRR" | "ISK" | "JMD" | "JOD" | "JPY" | "KES" | "KGS" | "KHR" | "KMF" | "KPW" | "KRW" | "KWD" | "KYD" | "KZT" | "LAK" | "LBP" | "LKR" | "LRD" | "LSL" | "LYD" | "MAD" | "MDL" | "MGA" | "MKD" | "MMK" | "MNT" | "MOP" | "MRU" | "MUR" | "MVR" | "MWK" | "MXN" | "MXV" | "MYR" | "MZN" | "NAD" | "NGN" | "NIO" | "NOK" | "NPR" | "NZD" | "OMR" | "PAB" | "PEN" | "PGK" | "PHP" | "PKR" | "PLN" | "PYG" | "QAR" | "RON" | "RSD" | "RUB" | "RWF" | "SAR" | "SBD" | "SCR" | "SDG" | "SEK" | "SGD" | "SHP" | "SLE" | "SOS" | "SRD" | "SSP" | "STN" | "SVC" | "SYP" | "SZL" | "THB" | "TJS" | "TMT" | "TND" | "TOP" | "TRY" | "TTD" | "TWD" | "TZS" | "UAH" | "UGX" | "USD" | "USN" | "UYI" | "UYU" | "UYW" | "UZS" | "VES" | "VND" | "VUV" | "WST" | "XAF" | "XCD" | "XCG" | "XDR" | "XOF" | "XPF" | "XSU" | "YER" | "ZAR" | "ZMW" | "ZWG"
}Responses
201Shop created402The signup offer requires payment-method collection or is no longer available. Continue at /join.403409The domain already has a live binding. Owner details are included only when the caller may see the owning shop.
curl -X POST \
https://app.drip-apex.com/api/v1/shops \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "example_name",
"domain": "example_domain",
"platform": "shopify"
}'Get a shop
/shops/{id}
Responses
200Shop detail404
curl \
https://app.drip-apex.com/api/v1/shops/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update a shop
/shops/{id}
Request body
{
name?: string
domain?: string
origin?: string | null
tier?: "self_managed" | "apex_managed" | "drip_managed" | "core" | "managed" | "enterprise"
operator_enabled?: boolean
holdout_pct?: number
holdout_v1_enabled?: boolean
currency?: "AED" | "AFN" | "ALL" | "AMD" | "ANG" | "AOA" | "ARS" | "AUD" | "AWG" | "AZN" | "BAM" | "BBD" | "BDT" | "BGN" | "BHD" | "BIF" | "BMD" | "BND" | "BOB" | "BOV" | "BRL" | "BSD" | "BTN" | "BWP" | "BYN" | "BZD" | "CAD" | "CDF" | "CHE" | "CHF" | "CHW" | "CLF" | "CLP" | "CNY" | "COP" | "COU" | "CRC" | "CUP" | "CVE" | "CZK" | "DJF" | "DKK" | "DOP" | "DZD" | "EGP" | "ERN" | "ETB" | "EUR" | "FJD" | "FKP" | "GBP" | "GEL" | "GHS" | "GIP" | "GMD" | "GNF" | "GTQ" | "GYD" | "HKD" | "HNL" | "HRK" | "HTG" | "HUF" | "IDR" | "ILS" | "INR" | "IQD" | "IRR" | "ISK" | "JMD" | "JOD" | "JPY" | "KES" | "KGS" | "KHR" | "KMF" | "KPW" | "KRW" | "KWD" | "KYD" | "KZT" | "LAK" | "LBP" | "LKR" | "LRD" | "LSL" | "LYD" | "MAD" | "MDL" | "MGA" | "MKD" | "MMK" | "MNT" | "MOP" | "MRU" | "MUR" | "MVR" | "MWK" | "MXN" | "MXV" | "MYR" | "MZN" | "NAD" | "NGN" | "NIO" | "NOK" | "NPR" | "NZD" | "OMR" | "PAB" | "PEN" | "PGK" | "PHP" | "PKR" | "PLN" | "PYG" | "QAR" | "RON" | "RSD" | "RUB" | "RWF" | "SAR" | "SBD" | "SCR" | "SDG" | "SEK" | "SGD" | "SHP" | "SLE" | "SOS" | "SRD" | "SSP" | "STN" | "SVC" | "SYP" | "SZL" | "THB" | "TJS" | "TMT" | "TND" | "TOP" | "TRY" | "TTD" | "TWD" | "TZS" | "UAH" | "UGX" | "USD" | "USN" | "UYI" | "UYU" | "UYW" | "UZS" | "VES" | "VND" | "VUV" | "WST" | "XAF" | "XCD" | "XCG" | "XDR" | "XOF" | "XPF" | "XSU" | "YER" | "ZAR" | "ZMW" | "ZWG"
}Responses
200Shop updated403
curl -X PATCH \
https://app.drip-apex.com/api/v1/shops/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Pre-bind a Shopware store to an Apex shop
/shops/{id}/shopware/prebind
Request body
{
shopUrl: string (uri)
environmentKey?: string = "production"
}Responses
200Existing binding returned201Pending binding created400401403409429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/shopware/prebind \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shopUrl": "https://example.com",
"environmentKey": "example_environmentKey"
}'Get auto-stop settings
/shops/{id}/auto-stop
Responses
200Settings
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/auto-stop \
-H "Authorization: Bearer <apex-secret-key>"Update auto-stop settings
/shops/{id}/auto-stop
Request body
{
enabled?: boolean
mode?: "off" | "fixed_horizon_planned_end" | "sequential_significance"
min_runtime_minutes?: integer
sequential?: object
fixed_horizon?: object
}Responses
200Settings updated
curl -X PATCH \
https://app.drip-apex.com/api/v1/shops/exp_123/auto-stop \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get testing-defaults settings
/shops/{id}/testing-defaults
Responses
200Settings
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/testing-defaults \
-H "Authorization: Bearer <apex-secret-key>"Update testing-defaults settings
/shops/{id}/testing-defaults
Request body
{
significanceLevel?: number
statisticalPower?: number
sequentialTestingEnabled?: boolean
defaultPrimaryGoalId?: string (uuid) | null
}Responses
200Settings updated
curl -X PATCH \
https://app.drip-apex.com/api/v1/shops/exp_123/testing-defaults \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"significanceLevel": 1,
"statisticalPower": 1,
"sequentialTestingEnabled": false,
"defaultPrimaryGoalId": "uuid-here"
}'Get guardian settings
/shops/{id}/guardian
Responses
200Settings
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/guardian \
-H "Authorization: Bearer <apex-secret-key>"Update guardian settings
/shops/{id}/guardian
Request body
{
enabled?: boolean
pause_on_broken?: boolean
pause_on_harm?: boolean
alert_on_data_quality?: boolean
delivery_drift?: object
revenue_loss?: object | null
}Responses
200Settings updated
curl -X PATCH \
https://app.drip-apex.com/api/v1/shops/exp_123/guardian \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Get the store profile
/shops/{id}/store-profile
Responses
200The complete dashboard Store Profile contract. When the store-profile schema is not yet migrated, the route degrades to `{ schemaReady: false }` with HTTP 200 instead of failing.503Store profile temporarily unavailable (database under pressure); retry shortly.
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/store-profile \
-H "Authorization: Bearer <apex-secret-key>"Review a store-profile fact
/shops/{id}/store-profile/facts/{factId}
Request body
{
status: "suggested" | "confirmed" | "rejected" | "stale"
claim?: string
value?: any
}Responses
200Fact reviewed
curl -X PATCH \
https://app.drip-apex.com/api/v1/shops/exp_123/store-profile/facts/fact_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"status": "suggested",
"claim": "example_claim"
}'Review a semantic finding
/shops/{id}/store-profile/findings/review
Request body
{
key: string
claim: string
status?: string
title?: string
category?: string
source?: string
confidence?: number
value?: any
}Responses
200Finding reviewed
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/store-profile/findings/review \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"key": "example_key",
"claim": "example_claim"
}'Start a store-profile learning run
/shops/{id}/store-profile/learning-runs
Request body
{
trigger?: string = "manual"
sources?: string[]
scope?: string[]
}Responses
201Learning run queued
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/store-profile/learning-runs \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"trigger": "example_trigger",
"sources": [],
"scope": []
}'Issue a WooCommerce plugin install token
/shops/{id}/plugin-token
Requires shops:write. User API keys and OAuth bearer credentials also need the shop:settings role permission. Shop keys are limited to their own shop; bound OAuth/user keys cannot target another shop. Unbound user keys may target an accessible shop without X-Apex-Org. WooCommerce only. Rotates the same single-use token as the dashboard, expires in 48 hours, and replaces any unused token. Returns the configured plugin download URL and installation steps. Tokens are never included in audit logs. Launching tests is human-only.
Parameters
idin path required
Responses
200Single-use plugin token (Cache-Control: no-store)400401403404409Store is not WooCommerce429503Plugin download configuration or rate limiting backend unavailable
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/plugin-token \
-H "Authorization: Bearer <apex-secret-key>"Get engine state and guardrails
/shops/{id}/engine
Parameters
idin path required
Responses
200Engine state400401403404429503
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/engine \
-H "Authorization: Bearer <apex-secret-key>"Get engine guardrails
/shops/{id}/engine/guardrails
Parameters
idin path required
Responses
200Engine guardrails400401403404429503
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/guardrails \
-H "Authorization: Bearer <apex-secret-key>"Update engine guardrails with optimistic concurrency
/shops/{id}/engine/guardrails
Parameters
idin path required
Request body
{
expected_version: integer
allow?: object
auto_launch?: object
surfaces?: object
segments?: any
max_concurrent?: integer
max_per_page?: integer
}Responses
200Updated engine guardrails400401403404409422429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/guardrails \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_version": 1
}'Start engine
/shops/{id}/engine/start
Parameters
idin path required
Responses
200Engine state updated400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/start \
-H "Authorization: Bearer <apex-secret-key>"Pause engine
/shops/{id}/engine/pause
Parameters
idin path required
Responses
200Engine state updated400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/pause \
-H "Authorization: Bearer <apex-secret-key>"Stop engine
/shops/{id}/engine/stop
Parameters
idin path required
Responses
200Engine state updated400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/stop \
-H "Authorization: Bearer <apex-secret-key>"Approve engine proposal
/shops/{id}/engine/proposals/{pid}/approve
Parameters
idin path requiredpidin path required
Responses
200Engine proposal approve decision recorded400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/proposals/p_123/approve \
-H "Authorization: Bearer <apex-secret-key>"Reject engine proposal
/shops/{id}/engine/proposals/{pid}/reject
Parameters
idin path requiredpidin path required
Request body
{
reason: string
}Responses
200Engine proposal reject decision recorded400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/proposals/p_123/reject \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"reason": "example_reason"
}'Veto engine proposal
/shops/{id}/engine/proposals/{pid}/veto
Parameters
idin path requiredpidin path required
Responses
200Engine proposal veto decision recorded400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/proposals/p_123/veto \
-H "Authorization: Bearer <apex-secret-key>"Roll a certified Engine winner out to 100% of traffic
/shops/{id}/engine/interventions/{iid}/rollout
Sends all eligible traffic to the winning variation, freezes the certified interval, and starts the decay watch. The program holdout is untouched.
Parameters
idin path requirediidin path required
Request body
{
expected_updated_at: string (date-time)
}Responses
200Engine intervention rolled out400401403404409Rollout refused422429503Either the certified measurement could not be read (rollout refused, retriable) or the rate-limiting backend is unavailable. Both are plain error envelopes; only the first carries a rollout `code`.
curl -X POST \
https://app.drip-apex.com/api/v1/shops/exp_123/engine/interventions/i_123/rollout \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_updated_at": "example_expected_updated_at"
}'Read shop installation status
/install-status
User keys need shops:read; shop keys need experiments:read. User keys use their bound shop, or select a shop they can access with X-Apex-Shop (or shop_id) when unbound; the organization follows from the shop, so account-scoped keys need no X-Apex-Org. Without any shop the route answers 400 with code shop_context_required (connectors call create_shop first). Uses the shared 60-minute customer telemetry window. Returns only shop_id, overall, last_event_time, connected, receivingData, optional domains, next_action and storefront_check (present only with steps=1&live=1). Unavailable telemetry is explicit and never a healthy default, and returns an empty domains list when per-domain detail is unavailable. With steps=1 it adds window_days, checked_at and steps: one row per install step (script, consent, environments, orders, qa_access) with state done, waiting, not_applicable or unavailable, first and last seen within the 7-day window, a detail sentence and evidence naming its source (events, page, firewall or none). live=1 also checks the storefront HTML and firewall access with the shop's verification header, sent only to the shop's own HTTPS hosts and never returned. Verified requires a visitor page view on the store's own address: not a bot, not QA, no QA session or live run, sent by the Apex script rather than the Shopify pixel, after the active install. A myshopify.com host counts only when it is the store's only domain; shopifypreview.com never counts. Theme editor or preview traffic leaves overall at partial. With steps=1 the script and consent steps follow the same visitor rule. live=1 also runs Apex's live storefront check and returns storefront_check: checking, on, not_saved, password, password_seen, challenge or unavailable, with the final URL and HTTP status.
Parameters
stepsin querylivein query - With steps=1, also fetch the live storefront and test firewall access.
Responses
200Installation verification400401403429503
curl \
https://app.drip-apex.com/api/v1/install-status \
-H "Authorization: Bearer <apex-secret-key>"Read shop installation instructions
/shops/{id}/install-instructions
User keys need shops:read; shop keys need experiments:read. Returns platform-specific plain instructions and the canonical snippet; Shopify OAuth is included only for shopify-platform shops. Bound keys may read their shop only; unbound user keys may read any shop they can access, and account-scoped keys need no X-Apex-Org.
Parameters
idin path requiredconsent_providerin queryconsent_vendor_idin queryconsent_setting_keyin querybot_protectionin query - Comma-separated, case-insensitive: cloudflare, akamai, other. Empty items are ignored; any other value answers 400.toolsin query - Comma-separated testing tools already on the site, for example statsig,gempages.ga4_purchasein query
Responses
200Installation instructions400401403404429503
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/install-instructions \
-H "Authorization: Bearer <apex-secret-key>"Audit logs
List audit logs for an authorized shop with a user API key
/shops/{id}/audit-logs
Parameters
entity_typein queryentity_idin querylimitin queryoffsetin query
Responses
200Audit log page403404
curl \
https://app.drip-apex.com/api/v1/shops/exp_123/audit-logs \
-H "Authorization: Bearer <apex-secret-key>"List audit logs
/audit-logs
Parameters
entity_typein queryentity_idin querylimitin queryoffsetin query
Responses
200Audit log page403
curl \
https://app.drip-apex.com/api/v1/audit-logs \
-H "Authorization: Bearer <apex-secret-key>"Test results
List past test results
/test-results
Read imported and native results for the key's shop. Requires experiments:read for shop and user keys. Results are ordered by created_at descending, then id descending.
Parameters
sourcein querylimitin querycursorin query - Last row UUID from this shop. Invalid or foreign cursors return 400.
Responses
200Past test results400401403404429503
curl \
https://app.drip-apex.com/api/v1/test-results \
-H "Authorization: Bearer <apex-secret-key>"Remove an imported past test result
/test-results/{id}
Permanently remove one imported result from the key's shop. Requires experiments:write and confirm:true. The full record is retained in the audit log. Native results return 409; absent or foreign rows return 404; an unavailable required audit returns 500 without removing anything.
Parameters
idin path required - UUID, legacy ID or test key
Request body
{
confirm: true
}Responses
200Removed test result400401403404409422429500Removal not recorded; nothing was removed503
curl -X DELETE \
https://app.drip-apex.com/api/v1/test-results/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"confirm": true
}'Get a past test result
/test-results/{id}
Read a result by UUID, case-sensitive legacy ID or test key for the key's shop. Requires experiments:read. Absent or foreign rows return 404.
Parameters
idin path required - UUID, legacy ID or test key
Responses
200Past test result400401403404429503
curl \
https://app.drip-apex.com/api/v1/test-results/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Inspect structured test-result files
/test-results/import/inspect
Responses
200Import columns inspected400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/test-results/import/inspect \
-H "Authorization: Bearer <apex-secret-key>"Preview a structured test-result import
/test-results/import/preview
Responses
200Import preview400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/test-results/import/preview \
-H "Authorization: Bearer <apex-secret-key>"Confirm a test-result import
/test-results/import/confirm
Request body
{
rows: object[]
}Responses
200Imported test results400401403404409422429503
curl -X POST \
https://app.drip-apex.com/api/v1/test-results/import/confirm \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"rows": []
}'Analytics
Show entry and exit page analytics
/analytics/entry-exit
Parameters
daysin query
Responses
200Analytics data401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/entry-exit \
-H "Authorization: Bearer <apex-secret-key>"Show geography and device analytics
/analytics/geo
Parameters
daysin query
Responses
200Analytics data401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/geo \
-H "Authorization: Bearer <apex-secret-key>"Inspect retained order journey evidence
/analytics/order-journey
Secret or authorized user key only. Exactly one order identifier. Maximum 31 days; to cannot exceed server time by five minutes. Unknown and duplicate query fields are rejected. Read-only, private,no-store. Source failures return partial evidence with null counts. Eligibility is only for attributed_goal_counts, never final statistical contribution. Legacy consentedOrders/declinedOrders count identity presence, not historical consent. Retained processing receipts are compared per stage and assignment. Present goal-reader receipts identify one read-only snapshot and do not repair historical evidence. Goal-reader receipts exist only for orders inside the counted population; an excluded order reports its exclusion status without a result receipt. Transport success does not certify the integration.
Parameters
environmentin query requiredfromin query requiredinclude_qain queryorder_numberin queryplatformin query requiredplatform_order_idin querytoin query required
Responses
200Response400Invalid bounded query401Authentication required403Secret analytics:read access required404Shop not found429Rate limited503Integration profile unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/order-journey \
-H "Authorization: Bearer <apex-secret-key>"Show page analytics
/analytics/pages
Parameters
daysin queryplanningDaysin query - Planning window in days (planning_days is also accepted)limitin query
Responses
200Analytics data401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/pages \
-H "Authorization: Bearer <apex-secret-key>"Read store profit
/analytics/profit
Parameters
fromin query requiredtoin query required
Responses
200Store profit400401403429503
curl \
https://app.drip-apex.com/api/v1/analytics/profit \
-H "Authorization: Bearer <apex-secret-key>"Show realtime analytics
/analytics/realtime
Responses
200Analytics data401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/realtime \
-H "Authorization: Bearer <apex-secret-key>"Show session analytics
/analytics/sessions
Parameters
daysin query
Responses
200Analytics data401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/sessions \
-H "Authorization: Bearer <apex-secret-key>"Show dashboard analytics stats
/analytics/stats
Parameters
daysin queryshop_currencyin query
Responses
200Analytics data400401403422429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/stats \
-H "Authorization: Bearer <apex-secret-key>"Suggest goals and funnels
/analytics/suggest-goals
Requires a secret API key with the analytics:read scope. The endpoint only reads analytics and returns suggestions — it creates no goals — but it spends model budget, so publishable keys receive 403 Forbidden and a per-shop 24-hour spend guard applies on top of the normal API rate limit.
Responses
200Suggested goals and funnels401403413Request body is too large429502The suggestion model returned unusable output503
curl -X POST \
https://app.drip-apex.com/api/v1/analytics/suggest-goals \
-H "Authorization: Bearer <apex-secret-key>"Show analytics timeseries
/analytics/timeseries
Parameters
daysin query
Responses
200Analytics data401403422429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/timeseries \
-H "Authorization: Bearer <apex-secret-key>"Show traffic sources
/analytics/traffic
Parameters
daysin query
Responses
200Analytics data401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/traffic \
-H "Authorization: Bearer <apex-secret-key>"Show URL inventory
/analytics/url-inventory
Returns a shaped Tinybird pageview URL inventory for the API key's shop. Requires a secret API key with analytics:read.
Parameters
daysin queryhostin querypathContainsin querylimitin queryoffsetin query
Responses
200URL inventory401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/url-inventory \
-H "Authorization: Bearer <apex-secret-key>"Show daily visitor series
/analytics/visitor-series
Parameters
daysin query
Responses
200Daily visitors and pageviews401403429503Analytics is temporarily unavailable
curl \
https://app.drip-apex.com/api/v1/analytics/visitor-series \
-H "Authorization: Bearer <apex-secret-key>"Read monthly unique visitors for billing
/usage/unique-visitors
Each consented web visitor ID that loads at least one page with an active Apex script counts once per shop and calendar month in the shop’s analytics timezone (report timezone, then Shopify installation timezone, then UTC); detected bots, crawlers, preview, QA, non-production traffic and anonymous sentinel identities are excluded. Live exact counts. Defaults to 12 calendar months ending with the current month; explicit ranges may include at most 13 months. Requires a secret API key.
Parameters
fromin querytoin query
Responses
200Monthly unique visitors400Invalid month or range401403429503Analytics or shop timezone temporarily unavailable; never reported as zero
curl \
https://app.drip-apex.com/api/v1/usage/unique-visitors \
-H "Authorization: Bearer <apex-secret-key>"Brand
Get brand context
/brand-context
Returns the compiled shop brand context for external agents and developer workflows. Only kept v2 about/write content is included, with categories and content added to the existing response. Proposals, brand-book fields and internal Apex notes are never included.
Responses
200Compiled brand context401Unauthorized403API key is missing experiment read permission429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/brand-context \
-H "Authorization: Bearer <apex-secret-key>"Commerce
Look up Shopify products for commerce drafts
/commerce/catalog
Parameters
qin queryhandlein queryproduct_idin querycursorin querylimitin query
Responses
200Catalog lookup400401403404409422429502Shopify catalog read failed or returned an invalid price, currency or relationship (codes catalog_upstream_error, catalog_price_invalid, catalog_currency_unavailable, catalog_relationships_unavailable)503
curl \
https://app.drip-apex.com/api/v1/commerce/catalog \
-H "Authorization: Bearer <apex-secret-key>"Consent
Read consent gating settings
/consent
Responses
200Consent settings401403429503
curl \
https://app.drip-apex.com/api/v1/consent \
-H "Authorization: Bearer <apex-secret-key>"Update consent gating settings
/consent
Request body
{
applyCanonicalSplit?: true
consentMode?: object
}Responses
200Updated consent settings400401403429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/consent \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"applyCanonicalSplit": true,
"consentMode": {}
}'Environments
List runtime environments and domain mappings
/environments
Responses
200Environment settings401403429503
curl \
https://app.drip-apex.com/api/v1/environments \
-H "Authorization: Bearer <apex-secret-key>"Update runtime environments and domain mappings
/environments
Request body
{
environments?: Environments
expected_version?: string (date-time) | null
}Responses
200Updated environment settings and edge precompile outcome400The environment patch is invalid, including when the active key is absent from the effective environment items.401403409Runtime settings changed since the supplied version was read; re-read and retry.429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/environments \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expected_version": "example_expected_version"
}'Tracking
Export raw events
/events/export
Exports a shop's raw events in an absolute window using stable keyset pagination.
Parameters
fromin query requiredtoin query requiredexperimentin queryevent_typein query - Comma-separated event types.qain querylimitin querycursorin query - Opaque cursor returned by the previous page.
Responses
200Event export page400Invalid window, filter, limit, or cursor. invalid_window reasons: invalid_timestamp, to_not_after_from, window_too_long, or to_in_future.401Unauthorized403Missing experiment read permission429503504Tinybird query timed out
curl \
https://app.drip-apex.com/api/v1/events/export \
-H "Authorization: Bearer <apex-secret-key>"Discover the observed tracking schema
/tracking/discovery
Aggregates consented-session QA diagnostic events (qa_mode=1) and normal-traffic pageview denominators (qa_mode=0) for the authenticated API key's shop.
Parameters
minutesin querylimitin query
Responses
200Observed tracking schema report with URL push/pageview counts, timing percentiles, type-collision flags, and ranked storage keys401Unauthorized403API key is missing experiment read permission429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/tracking/discovery \
-H "Authorization: Bearer <apex-secret-key>"Read shop tracking health
/tracking/health
Returns independent installation, event, commerce, experiment-delivery and settings checks for the selected shop. Unavailable sources are explicit unknown checks and never healthy defaults.
Responses
200Per-shop tracking health400401403429503
curl \
https://app.drip-apex.com/api/v1/tracking/health \
-H "Authorization: Bearer <apex-secret-key>"Measure forced-preview versus natural decision latency before launch
/tracking/latency
Shop-scoped sdk_perf diagnostic buckets for page loads (perf_event_kind = 'init') only; route_change events are excluded. Forced contains the requested experiment in forced_experiment_ids. Natural includes shop loads with qa_mode=0 and no forced IDs. Optional QA session includes all loads for that session. Percentiles are null below five measured samples. Analytics failures return 503, never empty success.
Parameters
experiment_idin query required - Shop-owned experiment UUID, public ID, or test key.daysin queryqa_session_idin query
Responses
200Decision latency summary, milliseconds400Missing experiment or invalid window401Unauthorized403Missing experiments:read scope404Experiment not found in this shop429Rate limit exceeded503Decision latency analytics unavailable
curl \
https://app.drip-apex.com/api/v1/tracking/latency \
-H "Authorization: Bearer <apex-secret-key>"Simulate data-layer triggers
/tracking/simulate-trigger
Pure dry run of dataLayerTriggers against payloads. Does not persist or emit events.
Request body
{
payloads: any[]
triggers: object[]
}Responses
200Side-effect-free simulation verdict400401403413Request body exceeds 64KB429503
curl -X POST \
https://app.drip-apex.com/api/v1/tracking/simulate-trigger \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"payloads": [],
"triggers": []
}'Verify live tracking events
/tracking/verify
Returns recent Tinybird events for the API key's shop so agents can verify revenue, pageview, and custom tracking setup from the CLI.
Parameters
identityin query - Exact visitor_id or user_id, trimmed. Use the device id before identify and the app user id after identify. With identity, minutes defaults to 60 and is capped at 1440 (24 hours).minutesin query - Defaults to 120, or 60 with identity. Maximum 10080, or 1440 with identity.limitin queryexperiment_idin queryevent_typein query - Comma-separated event type filter.
Responses
200Tracking verification summary400Identity must contain 1 to 128 characters after trimming401Unauthorized403API key is missing experiment read permission429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/tracking/verify \
-H "Authorization: Bearer <apex-secret-key>"Forms
List tracked forms
/forms
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
rangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin query
Responses
200List tracked forms400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms \
-H "Authorization: Bearer <apex-secret-key>"Detect forms on a shop page
/forms/detect
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Request body
{
url: string (uri)
}Responses
200Detected forms400401403404409429502The page could not be inspected503
curl -X POST \
https://app.drip-apex.com/api/v1/forms/detect \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com"
}'Get a tracked form
/forms/{key}
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredrangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin query
Responses
200Get a tracked form400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms/apx_123 \
-H "Authorization: Bearer <apex-secret-key>"Create or restore a tracked form
/forms/{key}
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path required
Request body
{ $ref: "FormInput" }Responses
200Create or restore a tracked form201Create or restore a tracked form400401403404409429503
curl -X PUT \
https://app.drip-apex.com/api/v1/forms/apx_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Remove a tracked form
/forms/{key}
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path required
Responses
200Remove a tracked form400401403404409429503
curl -X DELETE \
https://app.drip-apex.com/api/v1/forms/apx_123 \
-H "Authorization: Bearer <apex-secret-key>"Form errors ranked by abandonment
/forms/{key}/errors
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredrangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin query
Responses
200Form errors ranked by abandonment400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms/apx_123/errors \
-H "Authorization: Bearer <apex-secret-key>"Form fields
/forms/{key}/fields
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredrangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin query
Responses
200Form fields400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms/apx_123/fields \
-H "Authorization: Bearer <apex-secret-key>"Form field detail
/forms/{key}/fields/{identity}
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredidentityin path requiredrangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin query
Responses
200Form field detail400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms/apx_123/fields/example_identity \
-H "Authorization: Bearer <apex-secret-key>"Label or merge a field
/forms/{key}/fields/{identity}
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredidentityin path required
Request body
{
label?: string | null
merged_into?: string | null
}Responses
200Label or merge a field400401403404409429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/forms/apx_123/fields/example_identity \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"label": "example_label",
"merged_into": "example_merged_into"
}'Form session identity paths
/forms/{key}/sessions
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredrangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin queryoutcomein queryfieldin query - Only sessions whose field path includes this field identity.with_replayin query - `1` lists only sessions that have a replay.limitin querycursorin query
Responses
200Form session identity paths400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms/apx_123/sessions \
-H "Authorization: Bearer <apex-secret-key>"Form summary and checkout steps
/forms/{key}/summary
Shop-scoped form analytics. Read failures return 503; rates with no denominator are null. Split results describe versions without deciding a winner.
Parameters
keyin path requiredrangein querysplitin querydevicein querycountryin queryinclude_qain queryinclude_removedin query
Responses
200Form summary and checkout steps400401403404409429503
curl \
https://app.drip-apex.com/api/v1/forms/apx_123/summary \
-H "Authorization: Bearer <apex-secret-key>"Funnels
Run funnel analysis
/funnels/{id}/run
Parameters
idin path requireddaysin query
Responses
200Funnel run400401403404429502Analysis unavailable503
curl \
https://app.drip-apex.com/api/v1/funnels/exp_123/run \
-H "Authorization: Bearer <apex-secret-key>"Goals
List goals
/goals
Returns a paginated list of goals for the shop.
Parameters
cursorin queryinclude_archivedin query - Include archived goals.limitin query
Responses
200Paginated list of goals401Unauthorized429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/goals \
-H "Authorization: Bearer <apex-secret-key>"Create goal
/goals
Creates a new goal. Click goals require page_id plus selector or text_match. Pageview goals require page_id or url_pattern. Custom goals require event_name. Line-item filters are accepted for revenue goals and custom checkout_started/add_to_cart funnel-event goals.
Request body
{
capture?: GoalCapture[] | null
count_once?: boolean = false
event_name?: string | null
filter?: GoalFilter | null
measurement?: "conversion" | "value"
name: string
page_id?: string (uuid) | null
selector?: string | null
text_match?: string | null
type: "click" | "pageview" | "custom" | "revenue"
url_pattern?: string | null
value_property?: string | null
value_unit?: "currency" | "number" | "duration_seconds" | "percent" | null
}Responses
201Goal created400Validation error; a click goal without page_id returns code: page_required with a next hint explaining how to choose a page.
curl -X POST \
https://app.drip-apex.com/api/v1/goals \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "example_name",
"type": "click"
}'Get goal
/goals/{id}
Returns a single goal by ID.
Parameters
idin path required
Responses
200Goal details404Goal not found
curl \
https://app.drip-apex.com/api/v1/goals/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Update goal
/goals/{id}
Partially updates a goal. Line-item filters are accepted for revenue goals and custom checkout_started/add_to_cart funnel-event goals. Custom goals may not listen on reserved wire event names (purchase, pageview, ...): the resulting type/event_name combination is validated against the stored row and rejected with 400 (funnel-event goal names are the sanctioned exception). A material goal definition change clears QA approval and launch readiness on every referencing test in the same transaction, moving ready-to-launch tests back to draft. Each affected test receives an activity entry naming the goal and changed fields. Name-only, description-only and unchanged saves preserve readiness. An activity write failure rolls back the goal edit.
Parameters
idin path required
Request body
{
capture?: GoalCapture[] | null
count_once?: boolean
event_name?: string | null
filter?: GoalFilter | null
measurement?: "conversion" | "value"
name?: string
page_id?: string (uuid) | null
selector?: string | null
text_match?: string | null
type?: "click" | "pageview" | "custom" | "revenue"
url_pattern?: string | null
value_property?: string | null
value_unit?: "currency" | "number" | "duration_seconds" | "percent" | null
}Responses
200Goal updated400Validation error; a click goal without page_id returns code: page_required with a next hint explaining how to choose a page.404Goal not found
curl -X PATCH \
https://app.drip-apex.com/api/v1/goals/exp_123 \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Archive goal
/goals/{id}
Archives an unused goal. Refuses goals referenced by experiment links, personalizations, persisted test plans or workspace metric configuration; returns same-shop test ids, names and statuses.
Parameters
idin path required
Responses
200Goal deleted404Goal not found409Goal is in use. Remove its references before archiving.
curl -X DELETE \
https://app.drip-apex.com/api/v1/goals/exp_123 \
-H "Authorization: Bearer <apex-secret-key>"Restore goal
/goals/{id}/restore
Parameters
idin path required
Responses
200Goal restored404Goal not found
curl -X POST \
https://app.drip-apex.com/api/v1/goals/exp_123/restore \
-H "Authorization: Bearer <apex-secret-key>"Inbox
List the chat messages of an idea or experiment
/inbox/messages
Pass experiment_id or backlog_item_id. An experiment linked to an idea shares the idea's thread. The shop-wide account thread is retired and answers 410.
Parameters
experiment_idin querybacklog_item_idin query
Responses
200Inbox messages400403404410The account thread is retired
curl \
https://app.drip-apex.com/api/v1/inbox/messages \
-H "Authorization: Bearer <apex-secret-key>"Send a chat message to an idea or experiment thread
/inbox/messages
thread_type idea needs backlog_item_id; experiment needs experiment_id and lands in the idea's thread when the experiment is linked to one. account answers 410.
Request body
{
backlog_item_id?: string (uuid) | null
body: string
experiment_id?: string (uuid) | null
thread_type?: "account" | "experiment" | "idea" = "account"
}Responses
201Message created400403404410The account thread is retired
curl -X POST \
https://app.drip-apex.com/api/v1/inbox/messages \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"body": "example_body",
"thread_type": "account"
}'Mark inbox channels read
/inbox/read-state
Request body
anyResponses
200Read state updated400403404410The account thread is retired
curl -X POST \
https://app.drip-apex.com/api/v1/inbox/read-state \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{}'Inspection
Inspect a live shop page
/inspect
One-shot read-only Browserbase inspection without an experiment or variation. User keys require shops:read and shop experiment-read permission; shop keys require experiments:read. X-Apex-Shop selects the user key's authorized shop. Each route call consumes one UTC-day inspection (default allowance 10). Composite page and element commands run their paired inspections in one session. Remote browsers share a two-session shop limit and 10-minute TTL.
Request body
{
args?: object = {}
command: "page" | "element" | "page_map" | "sections" | "dom" | "styles" | "screenshot"
timeoutMs?: integer = 8000
url: string (uri)
viewport?: "desktop" | "mobile" = "desktop"
}Responses
200Bridge inspection result, with capped base64 PNG or JPEG data URLs for screenshots400Invalid request or missing shop context401Unauthorized403Insufficient permission or URL outside the shop's hosts409Remote page inspection is disabled429Daily inspection allowance, remote session capacity or key rate limit reached; error.message contains the reason502Browser returned no usable result or screenshot exceeded the size limit503
curl -X POST \
https://app.drip-apex.com/api/v1/inspect \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"command": "page",
"url": "https://example.com"
}'Install Domains
Read install domains and storefront origins
/install-domains
Responses
200Install domains401403404The API key's shop was not found.429503
curl \
https://app.drip-apex.com/api/v1/install-domains \
-H "Authorization: Bearer <apex-secret-key>"Replace install domains
/install-domains
Request body
{
domains: string[]
ifRevision?: string
}Responses
200Updated install domains and edge precompile outcome400401403404The API key's shop was not found.409Install domains changed since the supplied revision was read; re-read and retry.429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/install-domains \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"domains": [],
"ifRevision": "example_ifRevision"
}'Install
Assess a public storefront for Apex installation
/install/assess
Loads one public storefront through the guarded outbound fetch path and drafts an Apex installation assessment and client guide.
Request body
{
shop_id?: string (uuid)
url: string (uri)
}Responses
200Storefront installation assessment400
curl -X POST \
https://app.drip-apex.com/api/v1/install/assess \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"shop_id": "uuid-here",
"url": "https://example.com"
}'User keys
Inspect the current user API key identity
/me
Responses
200User key identity and live organization role401403429503
curl \
https://app.drip-apex.com/api/v1/me \
-H "Authorization: Bearer <apex-secret-key>"List every organization and shop available to a user API key
/me/context
Responses
200User key context with effective organization and shop roles401403429503
curl \
https://app.drip-apex.com/api/v1/me/context \
-H "Authorization: Bearer <apex-secret-key>"Notifications
Read ready email recipients and current shop members
/notifications/ready/email
User API key with team:read (GET) or team:write (PUT), plus members:manage on the pinned shop. No master notification setting exists. Email off clears recipients; Slack off removes only experiment.ready_to_launch. Slack must already be connected, and its pause state is preserved.
Parameters
shop_idin query requiredx-apex-shopin header required - Must match shop_id
Responses
200Current settings400401403404409422Recipient is not a current shop member429503
curl \
https://app.drip-apex.com/api/v1/notifications/ready/email \
-H "Authorization: Bearer <apex-secret-key>"Replace ready email recipients (empty means off)
/notifications/ready/email
User API key with team:read (GET) or team:write (PUT), plus members:manage on the pinned shop. No master notification setting exists. Email off clears recipients; Slack off removes only experiment.ready_to_launch. Slack must already be connected, and its pause state is preserved.
Parameters
shop_idin query requiredx-apex-shopin header required - Must match shop_id
Request body
{
recipient_user_ids: string (uuid)[]
version: integer
}Responses
200Current settings400401403404409422Recipient is not a current shop member429503
curl -X PUT \
https://app.drip-apex.com/api/v1/notifications/ready/email \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"recipient_user_ids": [],
"version": 1
}'Read the ready Slack event toggle
/notifications/ready/slack
User API key with team:read (GET) or team:write (PUT), plus members:manage on the pinned shop. No master notification setting exists. Email off clears recipients; Slack off removes only experiment.ready_to_launch. Slack must already be connected, and its pause state is preserved.
Parameters
shop_idin query requiredx-apex-shopin header required - Must match shop_id
Responses
200Current settings400401403404409422Recipient is not a current shop member429503
curl \
https://app.drip-apex.com/api/v1/notifications/ready/slack \
-H "Authorization: Bearer <apex-secret-key>"Toggle the ready Slack event on an existing destination
/notifications/ready/slack
User API key with team:read (GET) or team:write (PUT), plus members:manage on the pinned shop. No master notification setting exists. Email off clears recipients; Slack off removes only experiment.ready_to_launch. Slack must already be connected, and its pause state is preserved.
Parameters
shop_idin query requiredx-apex-shopin header required - Must match shop_id
Request body
{
ready_to_launch: boolean
version: string (date-time)
}Responses
200Current settings400401403404409422Recipient is not a current shop member429503
curl -X PUT \
https://app.drip-apex.com/api/v1/notifications/ready/slack \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"ready_to_launch": false,
"version": "example_version"
}'Roadmap
List approved roadmap ideas
/roadmap/approved
Returns approved backlog/roadmap ideas ready to build in Apex. This is the API used by `apex roadmap approved`.
Parameters
limitin querycursorin queryqin query - Search title, description, hypothesis, brief, and research notes.sortin query - Order field. `apex_score` (consultant-adjusted priority; the Likelihood to Win headline is backlog-relative) sorts unscored ideas last. `computed_score` is deprecated and kept for older callers.directionin queryhas_experimentin query - When false, only returns approved ideas without a linked experiment. When true, only returns ideas with a linked experiment.
Responses
200Approved roadmap ideas401Unauthorized403API key is missing backlog read permission429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/roadmap/approved \
-H "Authorization: Bearer <apex-secret-key>"Get roadmap build handoff
/roadmap/items/{id}/handoff
Returns the full build brief for an approved roadmap item, including hypothesis, target page, visual references, scoring, and Apex CLI workspace commands.
Parameters
idin path required
Responses
200Versioned roadmap build handoff401Unauthorized403API key is missing backlog read permission404Roadmap item not found429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/roadmap/items/exp_123/handoff \
-H "Authorization: Bearer <apex-secret-key>"Runtime settings
Read public runtime settings
/runtime-settings
Responses
200Parsed runtime settings with shop identity and opaque version401403429503
curl \
https://app.drip-apex.com/api/v1/runtime-settings \
-H "Authorization: Bearer <apex-secret-key>"Update data layer triggers, setup capture, or project code
/runtime-settings
Request body
{
dataLayerTriggers?: DataLayerTrigger[]
expectedVersion?: string | null
projectCode?: object
setupCapture?: object
}Responses
200Updated runtime settings and precompile result400401403409429503
curl -X PATCH \
https://app.drip-apex.com/api/v1/runtime-settings \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"dataLayerTriggers": [],
"expectedVersion": "example_expectedVersion",
"projectCode": {},
"setupCapture": {}
}'Apply a server-owned runtime integration recipe
/runtime-settings/recipes
Request body
{
expectedVersion?: string | null
params?: object
recipeId: "customDataLayerPurchase" | "ga4Ecommerce"
}Responses
200Updated runtime settings and precompile result400401403409429503
curl -X POST \
https://app.drip-apex.com/api/v1/runtime-settings/recipes \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"expectedVersion": "example_expectedVersion",
"params": {},
"recipeId": "customDataLayerPurchase"
}'SOS
Create SOS ticket
/sos
Creates a customer bug report or help request for shop API keys. User API keys belonging to a DRIP platform administrator may also create internal SOS types.
Request body
{
affected_url?: string (uri) | null
attachment_paths?: string[]
backlog_item_id?: string (uuid) | null
description: string
expected_behavior?: string
experiment_id?: string (uuid) | null
idempotency_key?: string
loom_url?: string (uri) | null
reproduction_steps?: string | null
sub_class?: string | null
type: "full_handover" | "design" | "development" | "qa" | "config" | "bug" | "help"
}Responses
200Reused internal SOS ticket or deduplicated customer ticket receipt. Replaying an unconfirmed customer report with the same key retries safely and may return its confirmed dedup receipt.201Internal SOS ticket or customer ticket created401Unauthorized403API key is missing permission or requested an internal SOS type429Rate limit exceeded502Jira create failed or remains unconfirmed (submission_unconfirmed). A replay with the same key searches for the existing ticket before creating again and may return a 200 dedup receipt.503SOS is disabled
curl -X POST \
https://app.drip-apex.com/api/v1/sos \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"description": "example_description",
"type": "full_handover"
}'Get signed SOS media upload targets
/sos/attachment-uploads
Upload directly to private Storage with PUT to signed_url, then send the returned paths as attachment_paths to POST /sos. Images are limited to 10 MB, videos to 50 MB, and each report to five files. Grants use Storage's default expiry. Requires the same support capability as POST /sos. Publishable keys cannot upload.
Request body
{
files: object[]
}Responses
200Signed upload targets400Invalid media declaration401Unauthorized403Publishable key, inaccessible shop, or missing support capability429Rate limit exceeded502Storage grant failed503
curl -X POST \
https://app.drip-apex.com/api/v1/sos/attachment-uploads \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"files": []
}'List my SOS tickets
/sos/mine
Lists SOS tickets created by the current API key.
Responses
200SOS tickets401Unauthorized403API key is missing experiment read permission429Rate limit exceeded503
curl \
https://app.drip-apex.com/api/v1/sos/mine \
-H "Authorization: Bearer <apex-secret-key>"Telemetry
Record a CLI invocation
/telemetry/cli
Accepts user and secret shop keys without a write scope. Duplicate invocation IDs are a no-op.
Request body
{
agent_host?: string
api_call_timings?: object[]
api_calls?: integer = 0
attempted?: string
client_version: string
command: string
credential_kind?: "user" | "shop" | "env"
duration_ms: integer
entity_id?: string (uuid)
entity_type?: "experiment" | "landing_page"
error_class?: "ok" | "validation" | "auth" | "not_found" | "rate_limited" | "conflict" | "server" | "network" | "timeout" | "confirmation_required" | "check_failed" | "unknown_command" | "internal"
exit_code: integer
first_error_code?: string
first_error_request_id?: string | null
first_error_route?: string
first_error_status?: integer
invocation_id: string (uuid)
json_mode?: boolean = false
node_version?: string
option_names?: string[] = []
org_id?: string (uuid)
platform?: string
session_id?: string
shop_id?: string (uuid)
tty?: boolean = false
}Responses
202Response400401403429503
curl -X POST \
https://app.drip-apex.com/api/v1/telemetry/cli \
-H "Authorization: Bearer <apex-secret-key>" \
-H "Content-Type: application/json" \
-d '{
"client_version": "example_client_version",
"command": "example_command",
"duration_ms": 1,
"exit_code": 1,
"invocation_id": "uuid-here"
}'