{"openapi":"3.1.0","info":{"title":"Apex A/B Testing API","version":"1.0.0","description":"API for managing A/B testing experiments, variations, goals, webhooks, and screenshots."},"servers":[{"url":"/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key in the format apx_sk_..."}},"responses":{"BadRequest":{"description":"The request body, query string, or path parameter is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed, expired, revoked, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"The requested resource was not found in the API key's shop scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"The request conflicts with the current resource state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimitExceeded":{"description":"Per-key rate limit exceeded. Retry after the reset time.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Configured request limit for the current window."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests remaining in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix timestamp when the current window resets."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Experiment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","nullable":true,"description":"Optional URL-safe slug for GitOps workflows"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","running","paused","completed"]},"targeting":{"type":"object","nullable":true},"traffic_allocation":{"type":"number","minimum":0,"maximum":1},"sequential_testing_enabled":{"type":"boolean","description":"Enable always-valid sequential significance (mSPRT)"},"auto_stop_override":{"$ref":"#/components/schemas/AutoStopOverride"},"planned_test":{"$ref":"#/components/schemas/PlannedTestSnapshot"},"store_url":{"type":"string","nullable":true},"scheduled_start_at":{"type":"string","format":"date-time","nullable":true},"started_at":{"type":"string","format":"date-time","nullable":true},"ended_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"variations":{"type":"array","items":{"$ref":"#/components/schemas/Variation"}},"goals":{"type":"array","items":{}}},"required":["id","name","status","traffic_allocation","created_at"]},"AutoStopOverride":{"type":"object","properties":{"mode":{"type":"string","enum":["inherit","off","fixed_horizon_planned_end","sequential_significance"]},"min_runtime_minutes":{"type":"integer","minimum":0},"sequential":{"type":"object","nullable":true,"properties":{"min_visitors":{"type":"integer","minimum":1},"min_conversions":{"type":"integer","minimum":1},"action_on_significant":{"type":"string","enum":["pause"]}}},"fixed_horizon":{"type":"object","nullable":true,"properties":{"window_days":{"type":"integer","minimum":1},"mde":{"type":"number","minimum":0.001,"maximum":1},"alpha":{"type":"number","minimum":0.001,"maximum":0.2},"power":{"type":"number","minimum":0.5,"maximum":0.99},"tail":{"type":"string","enum":["two-sided","one-sided"]},"auto_pause_on_inconclusive":{"type":"boolean"}}}},"required":["mode"]},"PlannedTestSnapshot":{"type":"object","nullable":true,"properties":{"computed_at":{"type":"string","format":"date-time"},"goal_id":{"type":"string","format":"uuid"},"baseline_rate":{"type":"number"},"window_days":{"type":"integer"},"mde":{"type":"number"},"alpha":{"type":"number"},"power":{"type":"number"},"tail":{"type":"string","enum":["two-sided","one-sided"]},"required_sample_per_variant":{"type":"integer"},"required_total_visitors":{"type":"integer"},"estimated_days":{"type":"number"},"evaluation":{"type":"object","nullable":true,"properties":{"evaluated_at":{"type":"string","format":"date-time"},"outcome":{"type":"string","enum":["significant_pause","inconclusive_pause","continue_inconclusive","continue_not_positive"]},"total_visitors_at_evaluation":{"type":"integer"},"winning_variation_id":{"type":"string","format":"uuid","nullable":true}}}}},"Mutation":{"type":"object","required":["selector","action"],"properties":{"selector":{"type":"string","description":"CSS selector targeting the element(s)"},"action":{"type":"string","enum":["text","setText","html","style","setStyle","attribute","remove","insertBefore","insertAfter"],"description":"Type of DOM mutation to apply"},"text":{"type":"string","description":"New text content (for text/setText actions)"},"html":{"type":"string","description":"HTML content (for html/insertBefore/insertAfter actions)"},"attribute":{"type":"object","description":"Legacy single-attribute form (kept for backward compatibility)","properties":{"name":{"type":"string"},"value":{"type":"string","nullable":true}},"required":["name","value"]},"attributes":{"type":"object","additionalProperties":{"type":["string","null"]},"description":"Key-value pairs of attributes to set"},"styles":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value pairs of CSS styles to apply"},"description":{"type":"string","description":"Human-readable description"}}},"Variation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"experiment_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"weight":{"type":"number","minimum":0,"maximum":1},"is_control":{"type":"boolean"},"mutations":{"type":"array","items":{"$ref":"#/components/schemas/Mutation"}},"custom_css":{"type":"string","nullable":true,"description":"Custom CSS injected as a <style> tag"},"custom_js":{"type":"string","nullable":true,"description":"Custom JavaScript executed after mutations"}},"required":["id","experiment_id","name","weight","is_control"]},"GoalCapture":{"type":"object","required":["property","source"],"properties":{"source":{"type":"string","enum":["attribute","textContent","dataset"],"description":"Where to read the value from"},"attribute":{"type":"string","description":"Required when source is attribute or dataset (e.g. href, data-product-id)"},"property":{"type":"string","description":"Property name in event data"}}},"Goal":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["click","pageview","custom","revenue"]},"selector":{"type":"string","nullable":true},"text_match":{"type":"string","nullable":true,"description":"Case-insensitive text match for click goals"},"url_pattern":{"type":"string","nullable":true},"event_name":{"type":"string","nullable":true},"count_once":{"type":"boolean"},"capture":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/GoalCapture"},"description":"Data capture rules — extract element data when goal fires"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","type","count_once","created_at"]},"Webhook":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"required":["id","url","events","active","created_at"]},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"webhook_id":{"type":"string","format":"uuid"},"event":{"type":"string"},"status":{"type":"string","enum":["queued","processing","delivered","failed","dead"]},"attempts":{"type":"integer"},"response_status":{"type":"integer","nullable":true},"last_error":{"type":"string","nullable":true},"next_attempt_at":{"type":"string","format":"date-time"},"processing_started_at":{"type":"string","format":"date-time","nullable":true},"delivered_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","webhook_id","event","status","attempts","created_at","updated_at"]},"ScreenshotJob":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"results":{"type":"array","items":{},"nullable":true},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"required":["job_id","status","created_at"]},"Error":{"type":"object","description":"All public API errors use this envelope: error.message is stable enough for humans, while error.status mirrors the HTTP status code.","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"}},"required":["message","status"]}},"required":["error"]},"PaginationMeta":{"type":"object","properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true}},"required":["has_more","next_cursor"]},"WorkspaceFile":{"type":"object","properties":{"path":{"type":"string","description":"Canonical workspace path such as src/index.js, metrics.json, targeting.json, goals/primary.json, or segments/audience.json."},"kind":{"type":"string"},"contentType":{"type":"string"},"content":{"type":"string"},"sha256":{"type":"string"},"sizeBytes":{"type":"integer"},"updatedAt":{"type":"string","format":"date-time"}},"required":["path","kind","contentType","content","sha256","sizeBytes"]},"WorkspacePayload":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"schemaVersion":{"type":"integer"},"status":{"type":"string"},"currentRevisionId":{"type":"string","format":"uuid","nullable":true},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","schemaVersion","status","updatedAt"]},"files":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceFile"}}},"required":["workspace","files"]},"WorkspaceRevision":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"sequence":{"type":"integer"},"title":{"type":"string"},"source":{"type":"string"},"summary":{"type":"string","nullable":true},"changed_paths":{"type":"array","items":{"type":"string"}},"build_result":{"type":"object","nullable":true},"created_by":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}},"required":["id","sequence","title","source","changed_paths","created_at"]},"WorkspaceBuildResult":{"type":"object","properties":{"ok":{"type":"boolean"},"mutationCount":{"type":"integer"},"customCssBytes":{"type":"integer"},"customJsBytes":{"type":"integer"},"artifacts":{"type":"object"},"attachedGoalIds":{"type":"array","items":{"type":"string","format":"uuid"}},"driftBefore":{"type":"object"},"drift":{"type":"object"},"errors":{"type":"array","items":{"type":"string"}}},"required":["ok"]}}},"paths":{"/roadmap/approved":{"get":{"summary":"List approved roadmap ideas","description":"Returns approved backlog/roadmap ideas ready to build in Apex. This is the API used by `apex roadmap approved`.","tags":["Roadmap"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":25,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Search title, description, hypothesis, brief, and research notes."},{"name":"has_experiment","in":"query","schema":{"type":"boolean"},"description":"When false, only returns approved ideas without a linked experiment. When true, only returns ideas with a linked experiment."}],"responses":{"200":{"description":"Approved roadmap ideas","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}},"required":["data","pagination"]}}}},"401":{"description":"Unauthorized"},"403":{"description":"API key is missing backlog read permission"},"429":{"description":"Rate limit exceeded"}}}},"/roadmap/items/{id}/handoff":{"get":{"summary":"Get roadmap build handoff","description":"Returns the full build brief for an approved roadmap item, including hypothesis, target page, visual references, scoring, and Apex CLI workspace commands.","tags":["Roadmap"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Roadmap build handoff","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"},"403":{"description":"API key is missing backlog read permission"},"404":{"description":"Roadmap item not found"},"429":{"description":"Rate limit exceeded"}}}},"/experiments":{"get":{"summary":"List experiments","description":"Returns a paginated list of experiments. Supports cursor-based pagination and optional status filter.","tags":["Experiments"],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["draft","running","paused","completed"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"},"description":"ID of the last item from the previous page"}],"responses":{"200":{"description":"Paginated list of experiments","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Experiment"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded"}}},"post":{"summary":"Create experiment","description":"Creates a new experiment. Default control and variant are created if variations are not provided.","tags":["Experiments"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"slug":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","nullable":true},"description":{"type":"string","maxLength":2000,"nullable":true},"traffic_allocation":{"type":"number","minimum":0,"maximum":1,"default":1},"sequential_testing_enabled":{"type":"boolean","description":"Enable always-valid sequential significance (mSPRT)"},"store_url":{"type":"string","format":"uri","nullable":true},"auto_stop_override":{"$ref":"#/components/schemas/AutoStopOverride"},"targeting":{"type":"object","nullable":true},"variations":{"type":"array","minItems":2,"items":{"type":"object","required":["name","weight"],"properties":{"name":{"type":"string"},"weight":{"type":"number","minimum":0,"maximum":1},"is_control":{"type":"boolean","default":false},"mutations":{"type":"array","items":{"$ref":"#/components/schemas/Mutation"},"default":[]},"custom_css":{"type":"string","nullable":true},"custom_js":{"type":"string","nullable":true}}}},"primary_goal_id":{"type":"string","format":"uuid","nullable":true,"description":"Explicit primary decision metric. Can reference any goal type, including click or pageview goals. Defaults to the first goal_ids item."},"goal_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"201":{"description":"Experiment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized"},"429":{"description":"Rate limit exceeded"}}}},"/experiments/{id}":{"get":{"summary":"Get experiment","description":"Returns a single experiment by ID with variations and goals.","tags":["Experiments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Experiment details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"404":{"description":"Experiment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update experiment","description":"Partially updates an experiment. Cannot update completed experiments.","tags":["Experiments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"slug":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","nullable":true},"description":{"type":"string","maxLength":2000,"nullable":true},"traffic_allocation":{"type":"number","minimum":0,"maximum":1},"sequential_testing_enabled":{"type":"boolean","description":"Enable always-valid sequential significance (mSPRT)"},"store_url":{"type":"string","format":"uri","nullable":true},"auto_stop_override":{"$ref":"#/components/schemas/AutoStopOverride"},"targeting":{"type":"object","nullable":true}}}}}},"responses":{"200":{"description":"Experiment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"400":{"description":"Cannot update completed experiment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Experiment not found"}}},"delete":{"summary":"Delete experiment","description":"Permanently deletes an experiment and its variations.","tags":["Experiments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Experiment deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"404":{"description":"Experiment not found"}}}},"/experiments/by-slug/{slug}":{"get":{"summary":"Get experiment by slug","description":"Fetch a single experiment by slug (scoped to the API key's shop).","tags":["Experiments"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"}}],"responses":{"200":{"description":"Experiment details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Experiment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded"}}},"put":{"summary":"Upsert experiment by slug","description":"Idempotently create or update a draft experiment by slug. If an experiment exists with this slug and is not in draft status, returns 409.","tags":["Experiments"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","variations"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","maxLength":2000,"nullable":true},"traffic_allocation":{"type":"number","minimum":0,"maximum":1,"default":1},"sequential_testing_enabled":{"type":"boolean","description":"Enable always-valid sequential significance (mSPRT)"},"store_url":{"type":"string","format":"uri","nullable":true},"auto_stop_override":{"$ref":"#/components/schemas/AutoStopOverride"},"targeting":{"type":"object","nullable":true},"variations":{"type":"array","minItems":2,"items":{"type":"object","required":["name","weight"],"properties":{"name":{"type":"string"},"weight":{"type":"number","minimum":0,"maximum":1},"is_control":{"type":"boolean","default":false},"mutations":{"type":"array","items":{"$ref":"#/components/schemas/Mutation"},"default":[]},"custom_css":{"type":"string","nullable":true},"custom_js":{"type":"string","nullable":true}}}},"primary_goal_id":{"type":"string","format":"uuid","nullable":true,"description":"Explicit primary decision metric. Can reference any goal type, including click or pageview goals. Defaults to the first goal_ids item."},"goal_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"Experiment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"201":{"description":"Experiment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized"},"409":{"description":"Conflict (experiment not in draft status or slug already exists)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded"}}}},"/experiments/{id}/pause":{"post":{"summary":"Pause experiment","description":"Pauses a running experiment.","tags":["Experiments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Experiment paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"400":{"description":"Only running experiments can be paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Experiment not found"}}}},"/experiments/{id}/complete":{"post":{"summary":"Complete experiment","description":"Marks a running or paused experiment as completed. Sets ended_at timestamp.","tags":["Experiments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Experiment completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"400":{"description":"Only running or paused experiments can be completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Experiment not found"}}}},"/experiments/{id}/duplicate":{"post":{"summary":"Duplicate experiment","description":"Creates a copy of the experiment in draft status with all variations and goal associations.","tags":["Experiments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Duplicate experiment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"404":{"description":"Experiment not found"}}}},"/experiments/generate":{"post":{"summary":"Generate experiment with AI","description":"Uses AI to generate an experiment configuration based on a store URL and prompt.","tags":["Experiments","AI"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["store_url","prompt"],"properties":{"store_url":{"type":"string","format":"uri"},"prompt":{"type":"string","minLength":1,"maxLength":5000},"primary_goal_id":{"type":"string","format":"uuid","nullable":true,"description":"Explicit primary decision metric. Can reference any goal type, including click or pageview goals. Defaults to the first goal_ids item."},"goal_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"traffic_allocation":{"type":"number","minimum":0,"maximum":1,"default":1},"targeting":{"type":"object","nullable":true},"screenshot_devices":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"AI-generated experiment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/experiments/{id}/variations/generate":{"post":{"summary":"Generate variation with AI","description":"Uses AI to generate a new variation for an existing experiment.","tags":["Variations","AI"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Experiment ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","minLength":1,"maxLength":5000}}}}}},"responses":{"201":{"description":"AI-generated variation created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Variation"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Experiment not found"}}}},"/variations":{"post":{"summary":"Create variation","description":"Creates a new variation for a draft experiment. Requires experiment_id in the body.","tags":["Variations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["experiment_id","name","weight"],"properties":{"experiment_id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":255},"weight":{"type":"number","minimum":0,"maximum":1},"is_control":{"type":"boolean","default":false},"mutations":{"type":"array","items":{"$ref":"#/components/schemas/Mutation"},"default":[]},"custom_css":{"type":"string","nullable":true},"custom_js":{"type":"string","nullable":true}}}}}},"responses":{"201":{"description":"Variation created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Variation"}}}},"400":{"description":"Validation error or experiment not in draft status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Experiment not found"}}}},"/variations/{id}":{"get":{"summary":"Get variation","description":"Returns a single variation by ID.","tags":["Variations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Variation details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Variation"}}}},"403":{"description":"Not authorized to access this variation"},"404":{"description":"Variation not found"}}},"patch":{"summary":"Update variation","description":"Partially updates a variation. Cannot update is_control or variations on completed experiments.","tags":["Variations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"weight":{"type":"number","minimum":0,"maximum":1},"mutations":{"type":"array","items":{"$ref":"#/components/schemas/Mutation"}},"custom_css":{"type":"string","nullable":true},"custom_js":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Variation updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Variation"}}}},"400":{"description":"Cannot modify completed experiment variations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not authorized"},"404":{"description":"Variation not found"}}},"delete":{"summary":"Delete variation","description":"Deletes a non-control variation from a draft experiment. Minimum 2 variations must remain.","tags":["Variations"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Variation deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"400":{"description":"Cannot delete control, non-draft experiment, or last two variations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not authorized"},"404":{"description":"Variation not found"}}}},"/experiments/{id}/variations/{variationId}/workspace":{"get":{"summary":"Read variation workspace","description":"Returns the canonical Apex workspace for an exact experiment variation. Used by `apex workspace checkout`.","tags":["Apex Workspace"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Experiment ID"},{"name":"variationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Variation ID"}],"responses":{"200":{"description":"Canonical workspace payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspacePayload"}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not authorized to access this variation"},"404":{"description":"Experiment variation workspace not found"},"429":{"description":"Rate limit exceeded"}}}},"/experiments/{id}/variations/{variationId}/files":{"get":{"summary":"List variation workspace files","description":"Lists canonical workspace files for an exact experiment variation.","tags":["Apex Workspace"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"variationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Workspace files","content":{"application/json":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceFile"}}},"required":["files"]}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not authorized to access this variation"},"404":{"description":"Experiment variation workspace not found"},"429":{"description":"Rate limit exceeded"}}}},"/experiments/{id}/variations/{variationId}/files/{path}":{"get":{"summary":"Read variation workspace file","description":"Reads one canonical workspace file. URL-encode the file path, for example src%2Findex.js.","tags":["Apex Workspace"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"variationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"path","in":"path","required":true,"schema":{"type":"string"},"description":"Canonical workspace path, URL-encoded. Supported roots: src, goals, segments, network, metrics.json, targeting.json, experiment.config.json, dev-doc.md."}],"responses":{"200":{"description":"Workspace file","content":{"application/json":{"schema":{"type":"object","properties":{"file":{"$ref":"#/components/schemas/WorkspaceFile"}},"required":["file"]}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not authorized to access this variation"},"404":{"description":"Workspace file not found"},"429":{"description":"Rate limit exceeded"}}},"put":{"summary":"Write variation workspace file","description":"Writes one canonical workspace file and records a workspace revision. Used by `apex workspace publish`.","tags":["Apex Workspace"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"variationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"path","in":"path","required":true,"schema":{"type":"string"},"description":"Canonical workspace path, URL-encoded. Supported roots: src, goals, segments, network, metrics.json, targeting.json, experiment.config.json, dev-doc.md."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string"},"source":{"type":"string","enum":["manual","operator","build","system"],"default":"operator"},"title":{"type":"string","minLength":1,"maxLength":255}}}}}},"responses":{"200":{"description":"Workspace file write result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"changed":{"type":"boolean"},"file":{"$ref":"#/components/schemas/WorkspaceFile"},"revision":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"sequence":{"type":"integer"},"title":{"type":"string"}}}},"required":["ok","changed","file"]}}}},"400":{"description":"Validation error or unsupported workspace path"},"401":{"description":"Unauthorized"},"403":{"description":"Not authorized to edit this variation"},"404":{"description":"Experiment variation workspace not found"},"429":{"description":"Rate limit exceeded"}}}},"/experiments/{id}/variations/{variationId}/build":{"post":{"summary":"Build variation workspace","description":"Projects canonical workspace files into Apex runtime state: mutations, CSS, JS, targeting, goals, and segments.","tags":["Apex Workspace"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"variationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Workspace build succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBuildResult"}}}},"400":{"description":"Workspace build failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceBuildResult"}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not authorized to edit this variation"},"404":{"description":"Experiment variation workspace not found"},"429":{"description":"Rate limit exceeded"}}}},"/experiments/{id}/variations/{variationId}/revisions":{"get":{"summary":"List variation workspace revisions","description":"Lists recent workspace revisions for an exact experiment variation.","tags":["Apex Workspace"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"variationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Workspace revisions","content":{"application/json":{"schema":{"type":"object","properties":{"revisions":{"type":"array","items":{"$ref":"#/components/schemas/WorkspaceRevision"}}},"required":["revisions"]}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not authorized to access this variation"},"404":{"description":"Experiment variation workspace not found"},"429":{"description":"Rate limit exceeded"}}}},"/goals":{"get":{"summary":"List goals","description":"Returns a paginated list of goals for the shop.","tags":["Goals"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Paginated list of goals","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Goal"}},"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"description":"Unauthorized"},"429":{"description":"Rate limit exceeded"}}},"post":{"summary":"Create goal","description":"Creates a new goal. Click goals require selector, pageview goals require url_pattern, custom goals require event_name.","tags":["Goals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"type":{"type":"string","enum":["click","pageview","custom","revenue"]},"selector":{"type":"string","nullable":true},"text_match":{"type":"string","nullable":true,"description":"Case-insensitive text match for click goals"},"url_pattern":{"type":"string","nullable":true},"event_name":{"type":"string","nullable":true},"count_once":{"type":"boolean","default":false},"capture":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/GoalCapture"},"description":"Data capture rules"}}}}}},"responses":{"201":{"description":"Goal created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Goal"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/goals/{id}":{"get":{"summary":"Get goal","description":"Returns a single goal by ID.","tags":["Goals"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Goal details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Goal"}}}},"404":{"description":"Goal not found"}}},"patch":{"summary":"Update goal","description":"Partially updates a goal.","tags":["Goals"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"type":{"type":"string","enum":["click","pageview","custom","revenue"]},"selector":{"type":"string","nullable":true},"text_match":{"type":"string","nullable":true,"description":"Case-insensitive text match for click goals"},"url_pattern":{"type":"string","nullable":true},"event_name":{"type":"string","nullable":true},"count_once":{"type":"boolean"},"capture":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/GoalCapture"},"description":"Data capture rules"}}}}}},"responses":{"200":{"description":"Goal updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Goal"}}}},"404":{"description":"Goal not found"}}},"delete":{"summary":"Delete goal","description":"Permanently deletes a goal.","tags":["Goals"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Goal deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"404":{"description":"Goal not found"}}}},"/experiments/{id}/screenshots":{"post":{"summary":"Create screenshot job","description":"Queues a screenshot capture job for the specified variations and devices.","tags":["Screenshots"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Experiment ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["devices","variation_ids"],"properties":{"devices":{"type":"array","minItems":1,"items":{"type":"string"}},"variation_ids":{"type":"array","minItems":1,"items":{"type":"string","format":"uuid"}}}}}}},"responses":{"202":{"description":"Screenshot job queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotJob"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Experiment not found"}}}},"/screenshots/{jobId}":{"get":{"summary":"Get screenshot job status","description":"Returns the current status and results of a screenshot job.","tags":["Screenshots"],"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Screenshot job details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotJob"}}}},"404":{"description":"Job not found"}}}},"/webhooks":{"get":{"summary":"List webhooks","description":"Returns all webhooks for the shop.","tags":["Webhooks"],"responses":{"200":{"description":"List of webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create webhook","description":"Registers a new webhook endpoint. URL must use HTTPS. Events: experiment.started, experiment.completed, experiment.significant, variation.winning, screenshots.completed, project.published.","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"Must use HTTPS"},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["experiment.started","experiment.completed","experiment.significant","variation.winning","screenshots.completed","project.published"]}}}}}}},"responses":{"201":{"description":"Webhook created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}":{"delete":{"summary":"Delete webhook","description":"Removes a webhook registration.","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string","format":"uuid"}}}}}},"404":{"description":"Webhook not found"}}}},"/webhooks/{id}/deliveries":{"get":{"summary":"List webhook deliveries","description":"Returns recent delivery attempts for one webhook. Delivery IDs are also sent as the X-Apex-Delivery-Id header on outbound webhook requests.","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["queued","processing","delivered","failed","dead"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"List of webhook delivery attempts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}},"404":{"description":"Webhook not found"}}}}}}