@shipeasy/openapi 1.5.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/chunk-EPLLHLZN.js +1616 -0
- package/dist/chunk-EPLLHLZN.js.map +1 -0
- package/dist/chunk-SPH5HWCD.js +3326 -0
- package/dist/chunk-SPH5HWCD.js.map +1 -0
- package/dist/client-pVkoo3Bf.d.ts +11186 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +215 -0
- package/dist/client.js.map +1 -0
- package/dist/custom.d.ts +64 -0
- package/dist/custom.js +292 -0
- package/dist/custom.js.map +1 -0
- package/dist/index.d.ts +29 -17
- package/dist/index.js +253 -2458
- package/dist/index.js.map +1 -0
- package/dist/node-context.d.ts +85 -0
- package/dist/node-context.js +110 -0
- package/dist/node-context.js.map +1 -0
- package/dist/schemas.d.ts +7920 -0
- package/dist/schemas.js +1083 -0
- package/dist/schemas.js.map +1 -0
- package/openapi.json +15297 -3044
- package/openapi.yaml +13979 -0
- package/package.json +37 -21
- package/spec/components/errors.yaml +53 -0
- package/spec/components/parameters.yaml +24 -0
- package/spec/components/responses.yaml +107 -0
- package/spec/components/schemas/alert-rules.yaml +175 -0
- package/spec/components/schemas/attributes.yaml +157 -0
- package/spec/components/schemas/common.yaml +87 -0
- package/spec/components/schemas/configs.yaml +371 -0
- package/spec/components/schemas/connectors.yaml +593 -0
- package/spec/components/schemas/errors-api.yaml +209 -0
- package/spec/components/schemas/events.yaml +321 -0
- package/spec/components/schemas/experiments.yaml +865 -0
- package/spec/components/schemas/gate-templates.yaml +198 -0
- package/spec/components/schemas/gates.yaml +890 -0
- package/spec/components/schemas/i18n.yaml +478 -0
- package/spec/components/schemas/keys.yaml +186 -0
- package/spec/components/schemas/killswitches.yaml +309 -0
- package/spec/components/schemas/metrics.yaml +547 -0
- package/spec/components/schemas/ops.yaml +1011 -0
- package/spec/components/schemas/projects.yaml +222 -0
- package/spec/components/schemas/search.yaml +46 -0
- package/spec/components/schemas/universes.yaml +127 -0
- package/spec/openapi.yaml +348 -0
- package/spec/paths/alert-rules.yaml +250 -0
- package/spec/paths/attributes.yaml +261 -0
- package/spec/paths/configs.yaml +744 -0
- package/spec/paths/connectors.yaml +748 -0
- package/spec/paths/errors.yaml +382 -0
- package/spec/paths/events.yaml +366 -0
- package/spec/paths/experiments.yaml +777 -0
- package/spec/paths/gate-templates.yaml +263 -0
- package/spec/paths/gates.yaml +451 -0
- package/spec/paths/i18n.yaml +544 -0
- package/spec/paths/keys.yaml +181 -0
- package/spec/paths/killswitches.yaml +557 -0
- package/spec/paths/metrics.yaml +302 -0
- package/spec/paths/ops.yaml +446 -0
- package/spec/paths/projects.yaml +210 -0
- package/spec/paths/search.yaml +62 -0
- package/spec/paths/universes.yaml +247 -0
- package/dist/resources/alert-rules.d.ts +0 -50
- package/dist/resources/alert-rules.js +0 -75
- package/dist/resources/configs.d.ts +0 -681
- package/dist/resources/configs.js +0 -509
- package/dist/resources/experiments.d.ts +0 -849
- package/dist/resources/experiments.js +0 -707
- package/dist/resources/gates.d.ts +0 -808
- package/dist/resources/gates.js +0 -522
- package/dist/resources/index.d.ts +0 -2749
- package/dist/resources/index.js +0 -2391
- package/dist/resources/killswitches.d.ts +0 -436
- package/dist/resources/killswitches.js +0 -514
- package/dist/resources/universes.d.ts +0 -185
- package/dist/resources/universes.js +0 -234
- package/dist/schemas/alert-rules.d.ts +0 -65
- package/dist/schemas/alert-rules.js +0 -37
- package/dist/schemas/configs.d.ts +0 -154
- package/dist/schemas/configs.js +0 -134
- package/dist/schemas/constants.d.ts +0 -6
- package/dist/schemas/constants.js +0 -7
- package/dist/schemas/experiments.d.ts +0 -229
- package/dist/schemas/experiments.js +0 -221
- package/dist/schemas/folder.d.ts +0 -10
- package/dist/schemas/folder.js +0 -8
- package/dist/schemas/gates.d.ts +0 -386
- package/dist/schemas/gates.js +0 -190
- package/dist/schemas/index.d.ts +0 -11
- package/dist/schemas/index.js +0 -762
- package/dist/schemas/killswitches.d.ts +0 -110
- package/dist/schemas/killswitches.js +0 -226
- package/dist/schemas/metric-name.d.ts +0 -5
- package/dist/schemas/metric-name.js +0 -6
- package/dist/schemas/pagination.d.ts +0 -37
- package/dist/schemas/pagination.js +0 -44
- package/dist/schemas/universes.d.ts +0 -35
- package/dist/schemas/universes.js +0 -55
- package/dist/transport.d.ts +0 -30
- package/dist/transport.js +0 -44
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
// src/resources/experiments.ts
|
|
2
|
-
import { z as z4 } from "zod";
|
|
3
|
-
|
|
4
|
-
// src/schemas/experiments.ts
|
|
5
|
-
import { z as z3 } from "zod";
|
|
6
|
-
|
|
7
|
-
// src/schemas/folder.ts
|
|
8
|
-
import { z } from "zod";
|
|
9
|
-
var folderSchema = z.string().max(256).regex(/^[a-zA-Z0-9_-]+$/, "Folder must be alphanumeric, `_` or `-` (no `/`).").nullable().optional().describe(
|
|
10
|
-
"Optional folder name used to group items in the dashboard. Part of the SDK lookup key: an item in folder `checkout` named `new-cart` is referenced as `checkout/new-cart` from the SDK."
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
// src/schemas/metric-name.ts
|
|
14
|
-
import { z as z2 } from "zod";
|
|
15
|
-
var metricNameSchema = z2.string().regex(/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)?$/).max(128);
|
|
16
|
-
|
|
17
|
-
// src/schemas/experiments.ts
|
|
18
|
-
var experimentInlineMetricSchema = z3.object({
|
|
19
|
-
name: metricNameSchema.optional(),
|
|
20
|
-
query: z3.string().min(1).max(4096).describe("Metric DSL string, e.g. `count_users(checkout_completed)`.")
|
|
21
|
-
});
|
|
22
|
-
var experimentNameSchema = z3.string().regex(/^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)?$/).max(128).describe(
|
|
23
|
-
"Stable experiment key. Single segment or `folder.name` (a-z, 0-9, `_`/`-`; max 128 chars). Used by SDKs as `Shipeasy.getExperiment(user, '<name>')`. Immutable after create."
|
|
24
|
-
);
|
|
25
|
-
var experimentGroupSchema = z3.object({
|
|
26
|
-
name: z3.string().min(1).max(64).describe("Group label (e.g. `control`, `treatment_a`). Max 64 chars."),
|
|
27
|
-
weight: z3.number().int().min(0).max(1e4).describe(
|
|
28
|
-
"Allocation weight in basis points (0\u201310000). The sum across all groups must equal exactly **10000** (100%)."
|
|
29
|
-
),
|
|
30
|
-
params: z3.record(z3.string(), z3.unknown()).default({}).describe(
|
|
31
|
-
"Per-group parameter values delivered to the SDK when a caller is hashed into this group. Keys must match the experiment's `params` schema."
|
|
32
|
-
)
|
|
33
|
-
}).describe("One variant in the experiment. Weights across all groups must sum to 10000.");
|
|
34
|
-
var paramSchemaSchema = z3.record(z3.string(), z3.enum(["string", "bool", "number"])).describe(
|
|
35
|
-
"Map of param-name \u2192 scalar type. Defines the shape of `groups[].params`. Example: `{ headline: 'string', show_cta: 'bool' }`."
|
|
36
|
-
);
|
|
37
|
-
var bucketBySchema = z3.string().min(1).max(128).regex(
|
|
38
|
-
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
|
|
39
|
-
"Must be a user-attribute name (letters, digits, underscore; not starting with a digit)."
|
|
40
|
-
).describe(
|
|
41
|
-
"User-attribute name used as the bucketing key \u2014 e.g. `user_id` (default), `session_id`, `device_id`, or a custom attribute like `company_id` to keep a whole org on one variant. Defaults to `user_id` when omitted."
|
|
42
|
-
);
|
|
43
|
-
var experimentCreateSchema = z3.object({
|
|
44
|
-
name: experimentNameSchema,
|
|
45
|
-
description: z3.string().max(2e3).nullable().default(null).describe("Free-form description. Max 2000 chars, markdown rendered in the dashboard."),
|
|
46
|
-
hypothesis: z3.string().max(4e3).nullable().default(null).describe("Hypothesis statement shown in the editor. Display-only."),
|
|
47
|
-
tag: z3.string().max(64).nullable().default(null).describe("Short tag chip rendered next to the name. Display-only."),
|
|
48
|
-
owner_email: z3.string().max(254).nullable().default(null).describe("Owner email. Display-only."),
|
|
49
|
-
audience: z3.string().max(256).nullable().default(null).describe("Audience label shown in the editor. Display-only."),
|
|
50
|
-
bucket_by: bucketBySchema.nullable().default(null),
|
|
51
|
-
folder: folderSchema,
|
|
52
|
-
universe: z3.string().min(1).describe(
|
|
53
|
-
"Name of an existing universe in the project. Returns `422` if the universe doesn't exist."
|
|
54
|
-
),
|
|
55
|
-
targeting_gate: z3.string().nullable().default(null).describe(
|
|
56
|
-
"Optional gate name. Only callers that pass the gate are enrolled in the experiment."
|
|
57
|
-
),
|
|
58
|
-
allocation_pct: z3.number().int().min(0).max(1e4).default(0).describe(
|
|
59
|
-
"Share of the (gated) audience allocated to the experiment, in basis points (0\u201310000 = 0%\u2013100%). `0` = unallocated. Immutable while the experiment is running."
|
|
60
|
-
),
|
|
61
|
-
salt: z3.string().min(1).max(64).optional().describe("Hash salt for bucketing. Auto-generated if omitted. Immutable while running."),
|
|
62
|
-
params: paramSchemaSchema.default({}),
|
|
63
|
-
groups: z3.array(experimentGroupSchema).min(2).describe(
|
|
64
|
-
"Two or more variants. Weights must sum to exactly 10000 (100%). Immutable while running."
|
|
65
|
-
),
|
|
66
|
-
significance_threshold: z3.number().min(1e-4).max(0.5).default(0.05).describe(
|
|
67
|
-
"p-value cutoff used by the analysis pass. Defaults to `0.05`. Values other than 0.05 require Pro plan or higher."
|
|
68
|
-
),
|
|
69
|
-
min_runtime_days: z3.number().int().min(0).max(365).default(0).describe("Minimum days the experiment must run before results are considered conclusive."),
|
|
70
|
-
min_sample_size: z3.number().int().min(1).default(100).describe("Minimum exposures per group before results are considered conclusive."),
|
|
71
|
-
sequential_testing: z3.boolean().default(false).describe(
|
|
72
|
-
"Enable sequential testing (always-valid p-values). Requires Premium plan or higher."
|
|
73
|
-
),
|
|
74
|
-
goal_metric: experimentInlineMetricSchema.optional().describe(
|
|
75
|
-
"Single goal metric defined inline as a DSL query. The underlying event is auto-created if missing."
|
|
76
|
-
),
|
|
77
|
-
guardrail_metrics: z3.array(experimentInlineMetricSchema).max(10).default([]).describe(
|
|
78
|
-
"Up to 10 guardrail metrics defined inline. Each is upserted (event + metric) and attached with role=guardrail."
|
|
79
|
-
)
|
|
80
|
-
}).refine(
|
|
81
|
-
(e) => e.groups.reduce((s, g) => s + g.weight, 0) === 1e4,
|
|
82
|
-
"groups weights must sum to exactly 10000"
|
|
83
|
-
).describe(
|
|
84
|
-
"Body for `POST /api/admin/experiments`. `name`, `universe`, and `groups` (\u22652, weights sum to 10000) required."
|
|
85
|
-
);
|
|
86
|
-
var experimentUpdateSchema = z3.object({
|
|
87
|
-
name: experimentNameSchema.optional(),
|
|
88
|
-
description: z3.string().max(2e3).nullable().optional(),
|
|
89
|
-
hypothesis: z3.string().max(4e3).nullable().optional(),
|
|
90
|
-
tag: z3.string().max(64).nullable().optional(),
|
|
91
|
-
owner_email: z3.string().max(254).nullable().optional(),
|
|
92
|
-
audience: z3.string().max(256).nullable().optional(),
|
|
93
|
-
bucket_by: bucketBySchema.nullable().optional(),
|
|
94
|
-
folder: folderSchema,
|
|
95
|
-
targeting_gate: z3.string().nullable().optional(),
|
|
96
|
-
allocation_pct: z3.number().int().min(0).max(1e4).optional().describe("Basis-points allocation (0\u201310000). Immutable while the experiment is running."),
|
|
97
|
-
salt: z3.string().min(1).max(64).optional().describe("Hash salt. Immutable while running."),
|
|
98
|
-
universe: z3.string().optional().describe(
|
|
99
|
-
"New universe name. Immutable while running. Returns `422` if the universe doesn't exist."
|
|
100
|
-
),
|
|
101
|
-
params: paramSchemaSchema.optional(),
|
|
102
|
-
groups: z3.array(experimentGroupSchema).min(2).optional().describe("Replacement groups. Weights must sum to 10000. Immutable while running."),
|
|
103
|
-
significance_threshold: z3.number().min(1e-4).max(0.5).optional(),
|
|
104
|
-
min_runtime_days: z3.number().int().min(0).max(365).optional(),
|
|
105
|
-
min_sample_size: z3.number().int().min(1).optional(),
|
|
106
|
-
sequential_testing: z3.boolean().optional(),
|
|
107
|
-
goal_metric: experimentInlineMetricSchema.optional().describe("Replaces the goal metric (event auto-upserted)."),
|
|
108
|
-
guardrail_metrics: z3.array(experimentInlineMetricSchema).max(10).optional().describe("Replaces the guardrail set wholesale (event auto-upserted per entry).")
|
|
109
|
-
}).describe(
|
|
110
|
-
"Body for `PATCH /api/admin/experiments/{id}`. Partial \u2014 only supplied fields change. `allocation_pct`, `groups`, `salt`, `universe`, `params` are immutable while the experiment is running (stop first)."
|
|
111
|
-
);
|
|
112
|
-
var experimentStatusUpdateSchema = z3.object({
|
|
113
|
-
status: z3.enum(["draft", "running", "stopped", "archived"]).describe(
|
|
114
|
-
"Target status. Allowed transitions: `draft \u2192 running`, `running \u2192 stopped`, `stopped \u2192 archived`, `draft \u2192 archived`, and `archived \u2192 draft` (restore \u2014 only if the experiment never started). Restarting an archived experiment directly is not allowed; restore it to draft first, then start."
|
|
115
|
-
)
|
|
116
|
-
}).describe("Body for `POST /api/admin/experiments/{id}/status`.");
|
|
117
|
-
var experimentMetricsUpdateSchema = z3.object({
|
|
118
|
-
metrics: z3.array(
|
|
119
|
-
z3.object({
|
|
120
|
-
metric_id: z3.string().describe("Existing metric id in the project."),
|
|
121
|
-
role: z3.enum(["goal", "guardrail", "secondary"]).describe(
|
|
122
|
-
"Metric role. `goal` drives the decision, `guardrail` blocks ship if degraded, `secondary` is informational."
|
|
123
|
-
)
|
|
124
|
-
})
|
|
125
|
-
).describe("Replacement metrics list \u2014 replaces the current attachments wholesale.")
|
|
126
|
-
}).describe(
|
|
127
|
-
"Body for `POST /api/admin/experiments/{id}/metrics`. Replaces the experiment's metric attachments wholesale. Returns `422` if any `metric_id` is unknown."
|
|
128
|
-
);
|
|
129
|
-
var experimentResponseSchema = z3.object({
|
|
130
|
-
id: z3.string().describe("Stable opaque experiment id (`exp_\u2026`)."),
|
|
131
|
-
name: experimentNameSchema,
|
|
132
|
-
description: z3.string().nullable(),
|
|
133
|
-
hypothesis: z3.string().nullable(),
|
|
134
|
-
tag: z3.string().nullable(),
|
|
135
|
-
ownerEmail: z3.string().nullable(),
|
|
136
|
-
audience: z3.string().nullable(),
|
|
137
|
-
bucketBy: bucketBySchema.nullable(),
|
|
138
|
-
folder: z3.string().nullable(),
|
|
139
|
-
status: z3.enum(["draft", "running", "stopped", "archived"]),
|
|
140
|
-
universe: z3.string().describe("Universe name this experiment draws from."),
|
|
141
|
-
targetingGate: z3.string().nullable(),
|
|
142
|
-
allocationPct: z3.number().int().describe("Allocation in basis points (0\u201310000)."),
|
|
143
|
-
salt: z3.string(),
|
|
144
|
-
params: z3.record(z3.string(), z3.enum(["string", "bool", "number"])),
|
|
145
|
-
groups: z3.array(experimentGroupSchema),
|
|
146
|
-
significanceThreshold: z3.number(),
|
|
147
|
-
minRuntimeDays: z3.number().int(),
|
|
148
|
-
minSampleSize: z3.number().int(),
|
|
149
|
-
sequentialTesting: z3.boolean(),
|
|
150
|
-
startedAt: z3.string().nullable().describe("ISO-8601 timestamp the experiment last transitioned to `running`, or `null`."),
|
|
151
|
-
stoppedAt: z3.string().nullable().optional(),
|
|
152
|
-
updatedAt: z3.string()
|
|
153
|
-
});
|
|
154
|
-
var experimentCreateResponseSchema = z3.object({
|
|
155
|
-
id: z3.string().describe("Newly assigned experiment id."),
|
|
156
|
-
name: experimentNameSchema
|
|
157
|
-
});
|
|
158
|
-
var experimentUpdateResponseSchema = z3.object({
|
|
159
|
-
id: z3.string().describe("Experiment id that was updated.")
|
|
160
|
-
});
|
|
161
|
-
var experimentDeleteResponseSchema = z3.object({ ok: z3.literal(true) });
|
|
162
|
-
var experimentStatusResponseSchema = z3.object({
|
|
163
|
-
id: z3.string(),
|
|
164
|
-
status: z3.enum(["draft", "running", "stopped", "archived"])
|
|
165
|
-
});
|
|
166
|
-
var experimentMetricsResponseSchema = z3.object({
|
|
167
|
-
id: z3.string(),
|
|
168
|
-
metrics: z3.array(
|
|
169
|
-
z3.object({
|
|
170
|
-
metric_id: z3.string(),
|
|
171
|
-
role: z3.enum(["goal", "guardrail", "secondary"])
|
|
172
|
-
})
|
|
173
|
-
)
|
|
174
|
-
});
|
|
175
|
-
var experimentResultRowSchema = z3.object({
|
|
176
|
-
metric: z3.string(),
|
|
177
|
-
group_name: z3.string(),
|
|
178
|
-
ds: z3.string().describe("Date slice (`YYYY-MM-DD`)."),
|
|
179
|
-
n: z3.number().nullable(),
|
|
180
|
-
mean: z3.number().nullable(),
|
|
181
|
-
delta_pct: z3.number().nullable(),
|
|
182
|
-
p_value: z3.number().nullable(),
|
|
183
|
-
srm_detected: z3.number().nullable().describe("`1` if sample-ratio mismatch detected, else `0`.")
|
|
184
|
-
});
|
|
185
|
-
var experimentResultsResponseSchema = z3.object({
|
|
186
|
-
experiment: z3.object({
|
|
187
|
-
id: z3.string(),
|
|
188
|
-
name: experimentNameSchema,
|
|
189
|
-
status: z3.enum(["draft", "running", "stopped", "archived"])
|
|
190
|
-
}),
|
|
191
|
-
results: z3.array(experimentResultRowSchema)
|
|
192
|
-
});
|
|
193
|
-
var experimentTimeseriesResponseSchema = z3.object({
|
|
194
|
-
experiment: z3.object({
|
|
195
|
-
id: z3.string(),
|
|
196
|
-
name: experimentNameSchema,
|
|
197
|
-
status: z3.enum(["draft", "running", "stopped", "archived"])
|
|
198
|
-
}),
|
|
199
|
-
series: z3.array(experimentResultRowSchema)
|
|
200
|
-
});
|
|
201
|
-
var experimentReanalyzeResponseSchema = z3.object({
|
|
202
|
-
id: z3.string(),
|
|
203
|
-
queued: z3.literal(true)
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// src/transport.ts
|
|
207
|
-
var ApiError = class extends Error {
|
|
208
|
-
constructor(message, status) {
|
|
209
|
-
super(message);
|
|
210
|
-
this.status = status;
|
|
211
|
-
this.name = "ApiError";
|
|
212
|
-
}
|
|
213
|
-
status;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// src/resources/experiments.ts
|
|
217
|
-
var experimentListResponseSchema = z4.object({
|
|
218
|
-
data: z4.array(experimentResponseSchema),
|
|
219
|
-
next_cursor: z4.string().nullable()
|
|
220
|
-
});
|
|
221
|
-
var BASE = "/api/admin/experiments";
|
|
222
|
-
function experimentsClient(t) {
|
|
223
|
-
async function list(opts = {}) {
|
|
224
|
-
const query = {};
|
|
225
|
-
if (opts.limit !== void 0) query.limit = String(opts.limit);
|
|
226
|
-
if (opts.cursor) query.cursor = opts.cursor;
|
|
227
|
-
return t.request("GET", BASE, void 0, query);
|
|
228
|
-
}
|
|
229
|
-
async function listAllWith(status) {
|
|
230
|
-
const out = [];
|
|
231
|
-
let cursor;
|
|
232
|
-
do {
|
|
233
|
-
const query = { limit: "500" };
|
|
234
|
-
if (cursor) query.cursor = cursor;
|
|
235
|
-
if (status) query.status = status;
|
|
236
|
-
const page = await t.request("GET", BASE, void 0, query);
|
|
237
|
-
out.push(...page.data);
|
|
238
|
-
cursor = page.next_cursor ?? void 0;
|
|
239
|
-
} while (cursor);
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
async function listAll() {
|
|
243
|
-
return listAllWith();
|
|
244
|
-
}
|
|
245
|
-
async function resolve(idOrName) {
|
|
246
|
-
try {
|
|
247
|
-
return await t.request("GET", `${BASE}/${idOrName}`);
|
|
248
|
-
} catch (err) {
|
|
249
|
-
if (!(err instanceof ApiError) || err.status !== 404) throw err;
|
|
250
|
-
}
|
|
251
|
-
const found = (await listAllWith()).find((e) => e.name === idOrName) ?? (await listAllWith("archived")).find((e) => e.name === idOrName);
|
|
252
|
-
if (!found) throw new ApiError(`Experiment '${idOrName}' not found`, 404);
|
|
253
|
-
return found;
|
|
254
|
-
}
|
|
255
|
-
async function setStatus(id, status) {
|
|
256
|
-
return t.request(
|
|
257
|
-
"POST",
|
|
258
|
-
`${BASE}/${id}/status`,
|
|
259
|
-
experimentStatusUpdateSchema.parse({ status })
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
list,
|
|
264
|
-
listAll,
|
|
265
|
-
resolve,
|
|
266
|
-
get: (id) => t.request("GET", `${BASE}/${id}`),
|
|
267
|
-
create: (input) => t.request("POST", BASE, experimentCreateSchema.parse(input)),
|
|
268
|
-
update: (id, input) => t.request("PATCH", `${BASE}/${id}`, experimentUpdateSchema.parse(input)),
|
|
269
|
-
delete: (id) => t.request("DELETE", `${BASE}/${id}`),
|
|
270
|
-
setStatus,
|
|
271
|
-
start: (id) => setStatus(id, "running"),
|
|
272
|
-
stop: (id) => setStatus(id, "stopped"),
|
|
273
|
-
archive: (id) => setStatus(id, "archived"),
|
|
274
|
-
restore: (id) => setStatus(id, "draft"),
|
|
275
|
-
setMetrics: (id, input) => t.request("POST", `${BASE}/${id}/metrics`, experimentMetricsUpdateSchema.parse(input)),
|
|
276
|
-
results: (id) => t.request("GET", `${BASE}/${id}/results`),
|
|
277
|
-
timeseries: (id, metric) => t.request(
|
|
278
|
-
"GET",
|
|
279
|
-
`${BASE}/${id}/timeseries`,
|
|
280
|
-
void 0,
|
|
281
|
-
metric ? { metric } : void 0
|
|
282
|
-
),
|
|
283
|
-
reanalyze: (id) => t.request("POST", `${BASE}/${id}/reanalyze`)
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
var SAMPLE_EXP = {
|
|
287
|
-
id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1",
|
|
288
|
-
name: "checkout_button_color",
|
|
289
|
-
description: "Test green vs. blue CTA on the checkout page.",
|
|
290
|
-
tag: "checkout",
|
|
291
|
-
status: "running",
|
|
292
|
-
universe: "primary_users",
|
|
293
|
-
targetingGate: null,
|
|
294
|
-
allocationPct: 5e3,
|
|
295
|
-
salt: "8d3e9a1f6b7c4a5fa1c2b6d3e7c8e3a1",
|
|
296
|
-
params: { cta_color: "string" },
|
|
297
|
-
groups: [
|
|
298
|
-
{ name: "control", weight: 5e3, params: { cta_color: "blue" } },
|
|
299
|
-
{ name: "treatment", weight: 5e3, params: { cta_color: "green" } }
|
|
300
|
-
],
|
|
301
|
-
significanceThreshold: 0.05,
|
|
302
|
-
minRuntimeDays: 7,
|
|
303
|
-
minSampleSize: 1e3,
|
|
304
|
-
sequentialTesting: false,
|
|
305
|
-
startedAt: "2026-05-01T12:00:00.000Z",
|
|
306
|
-
stoppedAt: null,
|
|
307
|
-
updatedAt: "2026-05-09T18:22:11.000Z"
|
|
308
|
-
};
|
|
309
|
-
var experimentsResource = {
|
|
310
|
-
name: "experiments",
|
|
311
|
-
basePath: BASE,
|
|
312
|
-
describeOne: "experiment",
|
|
313
|
-
describeMany: "experiments",
|
|
314
|
-
tag: {
|
|
315
|
-
name: "Experiments",
|
|
316
|
-
description: [
|
|
317
|
-
"A/B/n experiments: randomised group assignment plus the analysis pipeline (t-test, sequential testing, SRM detection) on top of a universe.",
|
|
318
|
-
"",
|
|
319
|
-
"**Identity.** Stable `name` (a-z, 0-9, `_`/`-`, max 64 chars). Immutable after create.",
|
|
320
|
-
"",
|
|
321
|
-
"**Lifecycle.** `draft \u2192 running \u2192 stopped \u2192 archived`. Transition via `POST /{id}/status`. An archived experiment that never started can be restored with `archived \u2192 draft`; restarting an archived experiment directly is not allowed.",
|
|
322
|
-
"",
|
|
323
|
-
"**Allocation.** `allocation_pct` (basis points, 0\u201310000) is the share of the targeted audience enrolled; `groups[].weight` (must sum to 10000) splits the enrolled audience. `targeting_gate` narrows the eligible audience before allocation.",
|
|
324
|
-
"",
|
|
325
|
-
"**Immutable while running.** `allocation_pct`, `groups`, `salt`, `universe`, `params` cannot be edited on a running experiment \u2014 stop it first.",
|
|
326
|
-
"",
|
|
327
|
-
"**Metrics.** Attach via `POST /{id}/metrics`. Each metric has a role: `goal` drives the decision, `guardrail` blocks ship on regression, `secondary` is informational.",
|
|
328
|
-
"",
|
|
329
|
-
"**Analysis.** Daily cron writes results to D1. Read via `GET /{id}/results` (latest per metric/group/day) or `GET /{id}/timeseries` (full history). `POST /{id}/reanalyze` requeues the analysis pass."
|
|
330
|
-
].join("\n")
|
|
331
|
-
},
|
|
332
|
-
schemas: {
|
|
333
|
-
create: experimentCreateSchema,
|
|
334
|
-
update: experimentUpdateSchema
|
|
335
|
-
},
|
|
336
|
-
actions: [
|
|
337
|
-
{ name: "start", description: "Transition draft experiment to running" },
|
|
338
|
-
{ name: "stop", description: "Stop a running experiment" },
|
|
339
|
-
{ name: "archive", description: "Archive a stopped experiment" },
|
|
340
|
-
{ name: "restore", description: "Restore an archived (never-started) experiment to draft" },
|
|
341
|
-
{ name: "reanalyze", description: "Re-run analysis pass for the experiment" }
|
|
342
|
-
],
|
|
343
|
-
endpoints: [
|
|
344
|
-
{
|
|
345
|
-
operationId: "listExperiments",
|
|
346
|
-
method: "GET",
|
|
347
|
-
path: "",
|
|
348
|
-
summary: "List experiments",
|
|
349
|
-
description: "Returns a single page of non-archived experiments ordered by `updated_at desc, id desc`. Use the `cursor` query parameter to paginate.",
|
|
350
|
-
response: experimentListResponseSchema,
|
|
351
|
-
examples: { response: { data: [SAMPLE_EXP], next_cursor: null } },
|
|
352
|
-
useCase: "Snapshot every active experiment in the project \u2014 e.g. render an overview dashboard or drive a CI check that no experiment has been running past its `min_runtime_days`."
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
operationId: "getExperiment",
|
|
356
|
-
method: "GET",
|
|
357
|
-
path: "/{id}",
|
|
358
|
-
summary: "Get one experiment",
|
|
359
|
-
description: "Returns the full experiment row including groups, params, allocation, and lifecycle timestamps.",
|
|
360
|
-
pathParams: { id: "Stable opaque experiment id (`exp_\u2026`) or the experiment's `name`." },
|
|
361
|
-
response: experimentResponseSchema,
|
|
362
|
-
examples: { response: SAMPLE_EXP },
|
|
363
|
-
useCase: "Fetch one experiment to render the detail page or to inspect its current allocation and group weights."
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
operationId: "createExperiment",
|
|
367
|
-
method: "POST",
|
|
368
|
-
path: "",
|
|
369
|
-
summary: "Create an experiment",
|
|
370
|
-
description: [
|
|
371
|
-
"Creates a new experiment in `draft` status. `name`, `universe`, and `groups` are required; everything else has sensible defaults.",
|
|
372
|
-
"",
|
|
373
|
-
"Returns `409` if `name` already exists, `422` if the named universe doesn't exist, `403` if a plan-gated option is set (`sequential_testing`, custom `significance_threshold`) on a plan that doesn't include it."
|
|
374
|
-
].join("\n"),
|
|
375
|
-
successStatus: 201,
|
|
376
|
-
request: experimentCreateSchema,
|
|
377
|
-
response: experimentCreateResponseSchema,
|
|
378
|
-
examples: {
|
|
379
|
-
requestExamples: {
|
|
380
|
-
minimal: {
|
|
381
|
-
summary: "Minimal \u2014 two-group 50/50",
|
|
382
|
-
description: "Smallest valid body. Two groups at 50/50, no allocation, no targeting.",
|
|
383
|
-
value: {
|
|
384
|
-
name: "checkout_button_color",
|
|
385
|
-
universe: "primary_users",
|
|
386
|
-
groups: [
|
|
387
|
-
{ name: "control", weight: 5e3 },
|
|
388
|
-
{ name: "treatment", weight: 5e3 }
|
|
389
|
-
]
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
targeted: {
|
|
393
|
-
summary: "Targeted \u2014 gate + allocation + params",
|
|
394
|
-
description: "Gate the audience with a `targeting_gate`, allocate 50% of it, and parameterise the variants.",
|
|
395
|
-
value: {
|
|
396
|
-
name: "checkout_button_color",
|
|
397
|
-
description: "Green CTA on checkout for Pro users.",
|
|
398
|
-
universe: "primary_users",
|
|
399
|
-
targeting_gate: "pro_plan_only",
|
|
400
|
-
allocation_pct: 5e3,
|
|
401
|
-
params: { cta_color: "string" },
|
|
402
|
-
groups: [
|
|
403
|
-
{ name: "control", weight: 5e3, params: { cta_color: "blue" } },
|
|
404
|
-
{ name: "treatment", weight: 5e3, params: { cta_color: "green" } }
|
|
405
|
-
]
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
|
-
multivariant: {
|
|
409
|
-
summary: "Multivariant \u2014 three groups",
|
|
410
|
-
description: "Three variants splitting 40/30/30. Requires the sum to equal 10000.",
|
|
411
|
-
value: {
|
|
412
|
-
name: "headline_test",
|
|
413
|
-
universe: "primary_users",
|
|
414
|
-
groups: [
|
|
415
|
-
{ name: "control", weight: 4e3 },
|
|
416
|
-
{ name: "alt_a", weight: 3e3 },
|
|
417
|
-
{ name: "alt_b", weight: 3e3 }
|
|
418
|
-
]
|
|
419
|
-
}
|
|
420
|
-
},
|
|
421
|
-
sequential: {
|
|
422
|
-
summary: "Sequential testing \u2014 Premium plan",
|
|
423
|
-
description: "Enable always-valid sequential tests. Requires Premium plan or higher.",
|
|
424
|
-
value: {
|
|
425
|
-
name: "checkout_button_color",
|
|
426
|
-
universe: "primary_users",
|
|
427
|
-
groups: [
|
|
428
|
-
{ name: "control", weight: 5e3 },
|
|
429
|
-
{ name: "treatment", weight: 5e3 }
|
|
430
|
-
],
|
|
431
|
-
sequential_testing: true,
|
|
432
|
-
min_runtime_days: 14
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
},
|
|
436
|
-
response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", name: "checkout_button_color" }
|
|
437
|
-
},
|
|
438
|
-
useCase: [
|
|
439
|
-
"- **Minimal 50/50** \u2014 `name` + `universe` + two equal-weight groups.",
|
|
440
|
-
"- **Targeted rollout** \u2014 supply `targeting_gate` to restrict the eligible audience and `allocation_pct` to enrol a slice of it.",
|
|
441
|
-
"- **Multivariant** \u2014 three or more groups with weights summing to 10000.",
|
|
442
|
-
"- **Sequential testing** \u2014 `sequential_testing: true` for Premium plans."
|
|
443
|
-
].join("\n")
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
operationId: "updateExperiment",
|
|
447
|
-
method: "PATCH",
|
|
448
|
-
path: "/{id}",
|
|
449
|
-
summary: "Update an experiment",
|
|
450
|
-
description: [
|
|
451
|
-
"Partial update. `allocation_pct`, `groups`, `salt`, `universe`, `params` are **immutable while running** \u2014 returns `409` if you try. Stop the experiment first.",
|
|
452
|
-
"",
|
|
453
|
-
"Editing `groups` while in `draft` is fine; weights must still sum to 10000."
|
|
454
|
-
].join("\n"),
|
|
455
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
456
|
-
request: experimentUpdateSchema,
|
|
457
|
-
response: experimentUpdateResponseSchema,
|
|
458
|
-
examples: {
|
|
459
|
-
requestExamples: {
|
|
460
|
-
rampAllocation: {
|
|
461
|
-
summary: "Ramp allocation \u2014 0 \u2192 50%",
|
|
462
|
-
description: "Bumps the share of the targeted audience enrolled. Allowed only when the experiment is in `draft`.",
|
|
463
|
-
value: { allocation_pct: 5e3 }
|
|
464
|
-
},
|
|
465
|
-
changeWeights: {
|
|
466
|
-
summary: "Change group weights",
|
|
467
|
-
description: "Replace the groups array \u2014 weights must still sum to 10000.",
|
|
468
|
-
value: {
|
|
469
|
-
groups: [
|
|
470
|
-
{ name: "control", weight: 2500 },
|
|
471
|
-
{ name: "treatment", weight: 7500 }
|
|
472
|
-
]
|
|
473
|
-
}
|
|
474
|
-
},
|
|
475
|
-
tighterSignificance: {
|
|
476
|
-
summary: "Tighter significance threshold",
|
|
477
|
-
description: "Drop the p-value cutoff from 0.05 \u2192 0.01. Requires Pro plan or higher.",
|
|
478
|
-
value: { significance_threshold: 0.01 }
|
|
479
|
-
},
|
|
480
|
-
updateMetadata: {
|
|
481
|
-
summary: "Update metadata",
|
|
482
|
-
description: "Change description/tag/targeting_gate freely on running experiments.",
|
|
483
|
-
value: {
|
|
484
|
-
description: "Bumped to 50% after passing canary on 25%.",
|
|
485
|
-
tag: "checkout"
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
},
|
|
489
|
-
response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1" }
|
|
490
|
-
},
|
|
491
|
-
useCase: [
|
|
492
|
-
"- **Update metadata** \u2014 `description`, `tag`, `targeting_gate` editable any time.",
|
|
493
|
-
"- **Ramp before launch** \u2014 set `allocation_pct` while still in `draft`.",
|
|
494
|
-
"- **Tighten significance** \u2014 `significance_threshold` (Pro+).",
|
|
495
|
-
"- **Rewire groups** \u2014 replace `groups` wholesale while in `draft`; immutable once running."
|
|
496
|
-
].join("\n")
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
operationId: "deleteExperiment",
|
|
500
|
-
method: "DELETE",
|
|
501
|
-
path: "/{id}",
|
|
502
|
-
summary: "Delete an experiment",
|
|
503
|
-
description: "Archives the experiment (soft-delete via status transition). Returns `409` if the experiment is still `running` \u2014 stop it first.",
|
|
504
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
505
|
-
response: experimentDeleteResponseSchema,
|
|
506
|
-
examples: { response: { ok: true } },
|
|
507
|
-
useCase: "Tear down an experiment after the analysis is signed off."
|
|
508
|
-
},
|
|
509
|
-
{
|
|
510
|
-
operationId: "setExperimentStatus",
|
|
511
|
-
method: "POST",
|
|
512
|
-
path: "/{id}/status",
|
|
513
|
-
summary: "Transition experiment status",
|
|
514
|
-
description: [
|
|
515
|
-
"Drives the experiment lifecycle. Allowed transitions:",
|
|
516
|
-
"",
|
|
517
|
-
"- `draft \u2192 running` \u2014 starts allocation. Bumps the `startedAt` timestamp.",
|
|
518
|
-
"- `running \u2192 stopped` \u2014 halts allocation. Existing exposures stay in the dataset.",
|
|
519
|
-
"- `stopped \u2192 archived` \u2014 soft-delete.",
|
|
520
|
-
"- `draft \u2192 archived` \u2014 discard an unstarted experiment.",
|
|
521
|
-
"- `archived \u2192 draft` \u2014 restore a soft-deleted experiment so it can be re-completed and started. Allowed only if it never started; one that already ran must be cloned instead.",
|
|
522
|
-
"",
|
|
523
|
-
"Restarting an `archived` experiment directly is not allowed \u2014 restore it to `draft` first. Returns `409` on illegal transitions and `429` if the plan's `experiments_running` limit is exceeded on `\u2192 running`."
|
|
524
|
-
].join("\n"),
|
|
525
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
526
|
-
request: experimentStatusUpdateSchema,
|
|
527
|
-
response: experimentStatusResponseSchema,
|
|
528
|
-
examples: {
|
|
529
|
-
requestExamples: {
|
|
530
|
-
start: {
|
|
531
|
-
summary: "Start \u2014 draft \u2192 running",
|
|
532
|
-
description: "Begins allocation. The experiment counts against the plan's `experiments_running` limit.",
|
|
533
|
-
value: { status: "running" }
|
|
534
|
-
},
|
|
535
|
-
stop: {
|
|
536
|
-
summary: "Stop \u2014 running \u2192 stopped",
|
|
537
|
-
description: "Halts allocation. Existing exposures remain analysable.",
|
|
538
|
-
value: { status: "stopped" }
|
|
539
|
-
},
|
|
540
|
-
archive: {
|
|
541
|
-
summary: "Archive \u2014 stopped \u2192 archived",
|
|
542
|
-
description: "Soft-deletes the experiment. List endpoints exclude archived rows by default.",
|
|
543
|
-
value: { status: "archived" }
|
|
544
|
-
},
|
|
545
|
-
restore: {
|
|
546
|
-
summary: "Restore \u2014 archived \u2192 draft",
|
|
547
|
-
description: "Un-deletes a soft-deleted experiment so it can be re-completed and started. Allowed only if it never ran.",
|
|
548
|
-
value: { status: "draft" }
|
|
549
|
-
}
|
|
550
|
-
},
|
|
551
|
-
response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", status: "running" }
|
|
552
|
-
},
|
|
553
|
-
useCase: [
|
|
554
|
-
'- **Start** \u2014 `{ "status": "running" }` after wiring up the SDK and verifying targeting on staging.',
|
|
555
|
-
'- **Stop** \u2014 `{ "status": "stopped" }` once the experiment hits its `min_runtime_days` and conclusive results land.',
|
|
556
|
-
'- **Archive** \u2014 `{ "status": "archived" }` to soft-delete after sign-off.'
|
|
557
|
-
].join("\n")
|
|
558
|
-
},
|
|
559
|
-
{
|
|
560
|
-
operationId: "setExperimentMetrics",
|
|
561
|
-
method: "POST",
|
|
562
|
-
path: "/{id}/metrics",
|
|
563
|
-
summary: "Attach metrics",
|
|
564
|
-
description: [
|
|
565
|
-
"Replaces the experiment's metric attachments wholesale. Each entry pairs an existing `metric_id` with a `role` (`goal` / `guardrail` / `secondary`).",
|
|
566
|
-
"",
|
|
567
|
-
"Returns `422` if any `metric_id` doesn't exist in the project. Pass `{ metrics: [] }` to detach everything."
|
|
568
|
-
].join("\n"),
|
|
569
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
570
|
-
request: experimentMetricsUpdateSchema,
|
|
571
|
-
response: experimentMetricsResponseSchema,
|
|
572
|
-
examples: {
|
|
573
|
-
requestExamples: {
|
|
574
|
-
standard: {
|
|
575
|
-
summary: "Standard \u2014 one goal + one guardrail",
|
|
576
|
-
description: "Attaches one decision metric and one safety metric.",
|
|
577
|
-
value: {
|
|
578
|
-
metrics: [
|
|
579
|
-
{ metric_id: "met_checkout_completed", role: "goal" },
|
|
580
|
-
{ metric_id: "met_page_errors", role: "guardrail" }
|
|
581
|
-
]
|
|
582
|
-
}
|
|
583
|
-
},
|
|
584
|
-
detach: {
|
|
585
|
-
summary: "Detach all",
|
|
586
|
-
description: "Send an empty array to clear every metric.",
|
|
587
|
-
value: { metrics: [] }
|
|
588
|
-
}
|
|
589
|
-
},
|
|
590
|
-
response: {
|
|
591
|
-
id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1",
|
|
592
|
-
metrics: [
|
|
593
|
-
{ metric_id: "met_checkout_completed", role: "goal" },
|
|
594
|
-
{ metric_id: "met_page_errors", role: "guardrail" }
|
|
595
|
-
]
|
|
596
|
-
}
|
|
597
|
-
},
|
|
598
|
-
useCase: [
|
|
599
|
-
"- **Standard setup** \u2014 one `goal`, one or two `guardrail`, optional `secondary` metrics for diagnostics.",
|
|
600
|
-
'- **Detach all** \u2014 send `{ "metrics": [] }` before archiving.'
|
|
601
|
-
].join("\n")
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
operationId: "getExperimentResults",
|
|
605
|
-
method: "GET",
|
|
606
|
-
path: "/{id}/results",
|
|
607
|
-
summary: "Get analysis results",
|
|
608
|
-
description: "Returns the latest analysis output for the experiment \u2014 one row per metric/group/day, including sample size, mean, % delta vs. control, p-value, and a sample-ratio mismatch flag.",
|
|
609
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
610
|
-
response: experimentResultsResponseSchema,
|
|
611
|
-
examples: {
|
|
612
|
-
response: {
|
|
613
|
-
experiment: {
|
|
614
|
-
id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1",
|
|
615
|
-
name: "checkout_button_color",
|
|
616
|
-
status: "running"
|
|
617
|
-
},
|
|
618
|
-
results: [
|
|
619
|
-
{
|
|
620
|
-
metric: "checkout_completed",
|
|
621
|
-
group_name: "control",
|
|
622
|
-
ds: "2026-05-09",
|
|
623
|
-
n: 12421,
|
|
624
|
-
mean: 0.1834,
|
|
625
|
-
delta_pct: null,
|
|
626
|
-
p_value: null,
|
|
627
|
-
srm_detected: 0
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
metric: "checkout_completed",
|
|
631
|
-
group_name: "treatment",
|
|
632
|
-
ds: "2026-05-09",
|
|
633
|
-
n: 12519,
|
|
634
|
-
mean: 0.1922,
|
|
635
|
-
delta_pct: 4.8,
|
|
636
|
-
p_value: 0.018,
|
|
637
|
-
srm_detected: 0
|
|
638
|
-
}
|
|
639
|
-
]
|
|
640
|
-
}
|
|
641
|
-
},
|
|
642
|
-
useCase: "Render the results table on the experiment detail page or drive an automated decision once a `goal` metric reaches significance."
|
|
643
|
-
},
|
|
644
|
-
{
|
|
645
|
-
operationId: "getExperimentTimeseries",
|
|
646
|
-
method: "GET",
|
|
647
|
-
path: "/{id}/timeseries",
|
|
648
|
-
summary: "Get analysis timeseries",
|
|
649
|
-
description: "Same row shape as `/results`, but returns every daily slice rather than the latest. Filter to a single metric with the `metric` query parameter.",
|
|
650
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
651
|
-
queryParams: {
|
|
652
|
-
metric: {
|
|
653
|
-
schema: z4.string().optional(),
|
|
654
|
-
description: "Optional metric name to filter the series."
|
|
655
|
-
}
|
|
656
|
-
},
|
|
657
|
-
response: experimentTimeseriesResponseSchema,
|
|
658
|
-
examples: {
|
|
659
|
-
response: {
|
|
660
|
-
experiment: {
|
|
661
|
-
id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1",
|
|
662
|
-
name: "checkout_button_color",
|
|
663
|
-
status: "running"
|
|
664
|
-
},
|
|
665
|
-
series: [
|
|
666
|
-
{
|
|
667
|
-
metric: "checkout_completed",
|
|
668
|
-
group_name: "treatment",
|
|
669
|
-
ds: "2026-05-08",
|
|
670
|
-
n: 11200,
|
|
671
|
-
mean: 0.1903,
|
|
672
|
-
delta_pct: 3.9,
|
|
673
|
-
p_value: 0.034,
|
|
674
|
-
srm_detected: 0
|
|
675
|
-
},
|
|
676
|
-
{
|
|
677
|
-
metric: "checkout_completed",
|
|
678
|
-
group_name: "treatment",
|
|
679
|
-
ds: "2026-05-09",
|
|
680
|
-
n: 12519,
|
|
681
|
-
mean: 0.1922,
|
|
682
|
-
delta_pct: 4.8,
|
|
683
|
-
p_value: 0.018,
|
|
684
|
-
srm_detected: 0
|
|
685
|
-
}
|
|
686
|
-
]
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
useCase: "Drive a chart of metric movement over the experiment runtime, or sanity-check the lift is monotonic before deciding."
|
|
690
|
-
},
|
|
691
|
-
{
|
|
692
|
-
operationId: "reanalyzeExperiment",
|
|
693
|
-
method: "POST",
|
|
694
|
-
path: "/{id}/reanalyze",
|
|
695
|
-
summary: "Re-queue analysis",
|
|
696
|
-
description: "Requeues the daily analysis pass for this experiment outside the normal cron cadence. Useful after attaching a new metric or correcting an event taxonomy. The job runs asynchronously.",
|
|
697
|
-
pathParams: { id: "Stable opaque experiment id." },
|
|
698
|
-
response: experimentReanalyzeResponseSchema,
|
|
699
|
-
examples: { response: { id: "exp_01j7wb12c3d4e5f6g7h8j9k0l1", queued: true } },
|
|
700
|
-
useCase: "Force-refresh results after wiring up a new metric without waiting for the next nightly cron tick."
|
|
701
|
-
}
|
|
702
|
-
]
|
|
703
|
-
};
|
|
704
|
-
export {
|
|
705
|
-
experimentsClient,
|
|
706
|
-
experimentsResource
|
|
707
|
-
};
|