# Create analyze

URL: https://docs.hanzo.ai/docs/openapi/experiment/post-experiment-by-id-analyze

> Per-variant conversion, lift and statistical significance against the control arm.

`POST /v1/experiment/{id}/analyze`

> [Experiment →](https://docs.hanzo.ai/docs/openapi/experiment) · [All API references →](https://docs.hanzo.ai/docs/openapi) · [Get started →](https://docs.hanzo.ai/docs/guides)

|               |                                                   |
| ------------- | ------------------------------------------------- |
| **Address**   | `https://api.hanzo.ai/v1/experiment/{id}/analyze` |
| **Method**    | `POST`                                            |
| **Operation** | `post_experiment_by_id_analyze`                   |
| **Auth**      | `Authorization: Bearer $HANZO_API_KEY`            |

Per-variant conversion, lift and statistical significance against
the control arm.

It reads per-subject outcomes from the analytics plane over a window, folds them
into per-variant samples, and returns each arm's exposed count, conversions,
rate, lift versus control, two-proportion z, two-tailed p-value and whether it
clears alpha. Arms with no data still appear with zero exposed, so the read is
complete over the experiment's declared arms; the control arm sorts first. The
pooled-variance estimator is used and the p-value is exact; a degenerate
comparison (an empty arm, no variance) answers z 0 and p 1 — not significant,
never an error.

Only EXPOSED subjects are counted, and each is joined to its arm by re-evaluating
the assignment flag AT ANALYSIS TIME — not from what was in force during the
window. That is the one rule to get right: analyzing an experiment after its
winner has been promoted re-buckets every subject into the promoted arm,
collapsing the control to zero exposed and making the result meaningless. Read
the analysis before deciding. A subject the flag cannot place is dropped rather
than allowed to poison the fold.

The winner in the response is ADVISORY — the significant, control-beating arm
with the highest rate, or empty when inconclusive. It promotes nothing; the
decision is a separate, explicit act.

Every plane read is scoped to the caller's org. Per-variant samples are also
written to the research evidence plane as immutable ab rows, best-effort: the
analysis is still returned if that write fails, because the samples are
recomputable, and the failure is logged rather than swallowed.

## Request

6 fields, body `application/json` (required).

| Field   | In   | Type            | Required | Description                                                                                                                                  |
| ------- | ---- | --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`    | path | string          | yes      | ID is the experiment the URL names.                                                                                                          |
| `alpha` | body | number (double) | —        | Alpha overrides the 0.05 two-tailed significance threshold when it lies strictly between 0 and 1; anything else leaves the default in place. |
| `days`  | body | integer (int64) | —        | Days is how far back to read when no start is given: 1 to 365, 30 by default.                                                                |
| `end`   | body | string          | —        | End is the window's exclusive end in RFC3339, defaulting to now.                                                                             |
| `id`    | body | string          | —        | ID is the experiment the URL names.                                                                                                          |
| `start` | body | string          | —        | Start is the window's inclusive start in RFC3339.                                                                                            |

## Response

| Status    | Body                | Meaning |
| --------- | ------------------- | ------- |
| `200`     | experiment.Analysis | ok      |
| `default` | problem-details     | refused |

`200` body — 15 fields.

| Field                   | In   | Type                  | Always | Description                                                                      |
| ----------------------- | ---- | --------------------- | ------ | -------------------------------------------------------------------------------- |
| `alpha`                 | body | number (double)       | —      | the two-tailed threshold significance was judged at                              |
| `experiment`            | body | string                | —      | the experiment that was analysed                                                 |
| `exposedTotal`          | body | integer (int64)       | —      | subjects enrolled across every arm                                               |
| `metric`                | body | string                | —      | the event a conversion is counted from                                           |
| `results`               | body | experiment.Outcome\[] | —      | one row per declared arm, control first                                          |
| `results[].control`     | body | boolean               | —      | true on the baseline arm; its own lift and stats are zero                        |
| `results[].converted`   | body | integer (int64)       | —      | of those, how many fired the metric event                                        |
| `results[].exposed`     | body | integer (int64)       | —      | subjects the arm enrolled — the denominator                                      |
| `results[].lift`        | body | number (double)       | —      | relative to control: (rate-ctrl)/ctrl                                            |
| `results[].pValue`      | body | number (double)       | —      | two-tailed p vs control                                                          |
| `results[].rate`        | body | number (double)       | —      | converted over exposed                                                           |
| `results[].significant` | body | boolean               | —      | pValue < alpha                                                                   |
| `results[].variant`     | body | string                | —      | the arm this row measures                                                        |
| `results[].z`           | body | number (double)       | —      | two-proportion z vs control                                                      |
| `winner`                | body | string                | —      | ADVISORY: the significant, control-beating arm with the highest rate, else empty |

Failure carries the platform error shape — see [Errors](https://docs.hanzo.ai/docs/errors).

## Examples

**CLI**

```bash
hanzo experiment analyze <id>
```

**SDK**

**.ts**

```ts
import { Configuration, ExperimentApi } from 'hanzoai';

const api = new ExperimentApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.postExperimentByIdAnalyze({ id: 'id', alpha: 0, days: 0 });
```

**.py**

```python
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import ExperimentApi

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = ExperimentApi(client).post_experiment_by_id_analyze(id='id', alpha=0, days=0)
```

**.go**

```go
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.ExperimentAPI.PostExperimentByIdAnalyze(context.Background()).Execute()
if err != nil {
	return err
}
```

**.rs**

```rust
use hanzo_client::apis::{configuration::Configuration, experiment_api};

let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();

let result = experiment_api::post_experiment_by_id_analyze(&cfg, Default::default()).await?;
```

**.java**

```java
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.ExperimentApi;

ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));

var result = new ExperimentApi(client).postExperimentByIdAnalyze();
```

**HTTP**

```bash
curl -X POST https://api.hanzo.ai/v1/experiment/<id>/analyze \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "alpha": 0,
       "days": 0
     }'
```

**MCP**

MCP reaches **experiment** through the `experiment` tool, which names its 7 operations with its own verbs — this one among them, under a name only MCP declares. `describe` explains any of them:

```bash
curl -X POST https://api.hanzo.ai/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "tools/call",
       "params": {
         "name": "describe",
         "arguments": {
           "op": "list_experiments"
         }
       }
     }'
```

---

[Experiment API](https://docs.hanzo.ai/docs/openapi/experiment) · [All Hanzo APIs](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
