Reference
REST API reference
Authentication, every endpoint, request and response shapes, errors and examples.
All endpoints live under the base URL shown on the experiment Setup tab, written here as https://YOUR-HOST/api/.... Requests and responses are JSON. Errors always have the shape { "error": "message" }.
Authentication#
| Used for | How to authenticate |
|---|---|
SDK endpoints (/api/assign, /api/track) | An SDK key in the X-API-Key header (or a ?key= query parameter). Secret keys are rejected here. |
Management endpoints (/api/experiments...) | A signed-in dashboard session, or a secret key: Authorization: Bearer cro_sk_.... A secret key only sees experiments in its own project. |
Key management (/api/keys...) | Dashboard session only. |
Keep secret keys on the server
A secret key can read and change every experiment in its project. Store it in an environment variable, never in client code or a repository.
Errors#
| Status | Meaning |
|---|---|
| 400 | Invalid input. The message names the field, for example variations: Experiment must have at least 2 variations. |
| 401 | Missing, malformed, unknown or revoked key, or not signed in. |
| 403 | The organization is suspended, a plan limit was reached, or the action is not allowed for this kind of caller. |
| 404 | Not found. Experiments in other projects also return 404. |
| 429 | Rate limit exceeded on SDK endpoints. A Retry-After header says how many seconds to wait. |
SDK endpoints#
These accept cross-origin requests from any site (CORS *) and answer OPTIONS preflights. They are rate limited per key and client IP.
POST /api/assign#
Returns the visitor's variation, creating the assignment if needed. The data is recorded in the environment of the key.
curl -X POST https://YOUR-HOST/api/assign \
-H 'Content-Type: application/json' \
-H 'X-API-Key: cro_pk_prod_...' \
-d '{"experiment_id": "exp_123", "user_id": "user_42"}'{
"experiment_id": "exp_123",
"variation_id": "var_456",
"variation_name": "Variant B",
"variation_url": "https://example.com/b",
"assigned": true
}assigned is false when the experiment is not running, the visitor is outside the traffic allocation, or the organization reached its monthly visitor limit (then reason is "quota_exceeded"). Visitors who already have an assignment keep it while the experiment runs, even after the limit is reached.
POST /api/track#
| Field | Type | Notes |
|---|---|---|
experiment_id | string | Required. |
variation_id | string | Required. Must belong to the experiment. |
user_id | string | Required. 1 to 200 printable ASCII characters. |
event_type | page_view or conversion | Required. |
event_name | string | Optional, up to 200 characters. |
metadata | object | Optional, under 4096 bytes as JSON. |
curl -X POST https://YOUR-HOST/api/track \
-H 'Content-Type: application/json' \
-H 'X-API-Key: cro_pk_prod_...' \
-d '{"experiment_id":"exp_123","variation_id":"var_456","user_id":"user_42","event_type":"conversion","event_name":"purchase"}'Returns 201 with { "success": true }.
Experiments#
GET /api/experiments#
Lists experiments you can access: { "experiments": [...] }.
curl https://YOUR-HOST/api/experiments \
-H 'Authorization: Bearer cro_sk_prod_...'POST /api/experiments#
Creates a draft experiment. Weights must add up to 100 and exactly one variation must be the control. Requires a dashboard session: creating experiments with a secret key returns 403 for now.
{
"name": "Pricing page headline",
"description": "A benefit-led headline will increase plan selections.",
"project_id": "prj_...", // optional, defaults to the selected project
"traffic_allocation": 100, // optional, 0 to 100, default 100
"variations": [
{ "name": "Control", "weight": 50, "is_control": true },
{ "name": "Variant B", "weight": 50, "is_control": false, "url": "https://example.com/b" }
]
}Returns 201 with { "experiment": {...}, "variations": [...] }.
GET /api/experiments/{id}#
Returns { "experiment": {...}, "variations": [...] }.
PATCH /api/experiments/{id}#
Updates name, description, traffic_allocation or status. Any other field is rejected. Status changes must follow the allowed transitions (draft to running, running to paused or completed, paused to running or completed).
curl -X PATCH https://YOUR-HOST/api/experiments/exp_123 \
-H 'Authorization: Bearer cro_sk_prod_...' \
-H 'Content-Type: application/json' \
-d '{"status": "running"}'DELETE /api/experiments/{id}#
Deletes a draft. Any other status returns 400.
GET /api/experiments/{id}/results#
Optional query parameter environment=dev|staging|prod. Without it, all environments are combined.
{
"experiment_id": "exp_123",
"experiment_name": "Pricing page headline",
"status": "running",
"sample_size": 2400,
"winner": "var_456", // present only when a variant is significantly better
"variations": [
{
"variation_id": "var_111", "variation_name": "Control", "is_control": true,
"total_users": 1200, "conversions": 60, "conversion_rate": 0.05,
"confidence_interval": { "lower": 0.039, "upper": 0.064 }
}
],
"statistical_significance": {
"is_significant": true, "p_value": 0.012, "confidence_level": 0.95, "z_score": -2.51
}
}z_score is computed as control minus variant, so a negative value means the variant converts better.
API keys#
Dashboard session only. GET /api/keys lists environments and keys (prefix only). POST /api/keys with { "environment_id": "env_...", "type": "sdk" | "secret", "label": "optional" } creates a key and returns the plaintext once. DELETE /api/keys/{id} revokes it immediately.