Introduction
The SplitStat API lets your own code do what the dashboard does: create experiments, metrics, audiences and events, read them back, and send events from your server.
A release pipeline can create a test when a build goes out. A nightly job can send refunds from your billing system, so a test is judged on what customers kept rather than what they first paid.
It is a REST API. You send JSON, you get JSON back, and the status codes mean what HTTP says they mean.
What it does not do
The API never decides which variation a visitor sees. The SplitStat script decides that in the browser, from settings it has already saved, without calling us.
This will not change. If the API answered that question, every page load on your site would wait on our servers. Your pages stay fast because nothing waits on us.
The base address
https://api.splitstat.com/v1
The version is part of the address. Inside v1 we only add things: a new
field, a new endpoint, a new allowed value. Nothing is renamed or removed, so
code you write against v1 keeps working.
The API has its own address, apart from your dashboard. It uses no cookies and no session, and it sends no CORS headers. That means a browser cannot call it. Call it from your server, where your key stays private.
Your first call
Make a key in the dashboard under Settings > API keys. Then list the projects it can reach:
curl https://api.splitstat.com/v1/projects \
-H "Authorization: Bearer ss_live_your_key_here"
{
"data": [
{
"id": "01J8Z4N1B6QRSTVWXYZ56789",
"name": "Main site",
"timezone": "Europe/London",
"currency": "GBP",
"created_at": "2026-09-20T09:40:11+00:00"
}
],
"links": { "first": null, "last": null, "prev": null, "next": null },
"meta": {
"path": "https://api.splitstat.com/v1/projects",
"per_page": 25,
"next_cursor": null,
"prev_cursor": null
}
}
Make this call first. A 200 means the key works, and the list shows which
projects it can reach: one, or every project in the account. A 401 means the
key is wrong, revoked or expired. The key's role is shown in Settings >
API keys.
How responses look
- One record comes back as
{ "data": { ... } }. - A list comes back as
{ "data": [ ... ] }, withlinksandmetabeside it. - Field names are
snake_case. - Times are ISO 8601, in UTC.
- Every
idis a 26 character ULID. Numeric ids never appear.
Lists come in pages
A list returns 25 records unless you ask for another number with limit. The
most you can ask for is 100. A larger number is treated as 100, not refused.
To get the next page, pass the next_cursor value from meta as cursor:
curl "https://api.splitstat.com/v1/projects?limit=100&cursor=eyJpZCI6..." \
-H "Authorization: Bearer ss_live_your_key_here"
When next_cursor is null, you have the last page.
Errors
Every error has the same shape:
{
"message": "This key is not valid. Check it, or make a new one in Settings.",
"code": "unauthenticated"
}
Write your code against code. It does not change. message is written for a
person, and we may reword it.
| Status | code | What it means |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. Nothing was read. |
| 401 | unauthenticated | The key is missing, wrong, revoked or expired. |
| 403 | forbidden | The key's role cannot do this. A viewer key tried to write. |
| 403 | feature_unavailable | Your plan does not include API access. |
| 403 | account_read_only | The account cannot make changes right now. Reads still work. |
| 404 | not_found | Nothing at that address, or it belongs to a project the key cannot reach. |
| 409 | invalid_state | The record cannot change in its current state. |
| 409 | metric_in_use | A test still uses this metric. |
| 409 | audience_in_use | A test still uses this audience. |
| 422 | validation_failed | A field is missing or wrong. errors lists each field and why. |
| 429 | rate_limited | Too many calls. Wait for the time in Retry-After. |
| 500 | server_error | A fault on our side. Try again; contact support if it keeps happening. |
A 422 also carries an errors object, keyed by field:
{
"message": "The name field is required.",
"code": "validation_failed",
"errors": {
"name": ["The name field is required."]
}
}
Rate limits
Limits are counted per key, so one runaway job does not slow your other integrations.
| Calls | Limit per key |
|---|---|
| Reads | 120 a minute |
| Creates, changes, deletes | 60 a minute |
Sending events (/track) | 600 a minute |
Each call to /track can carry up to 100 events.
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. A 429
also carries Retry-After, in seconds.
Getting access
API access depends on your plan. The pricing page lists
which plans include it. On a plan without it, every call answers 403 with
feature_unavailable.
Next steps
- Authentication: how to make a key, and what each key can do.
- API reference: every endpoint, with its fields and an example response.