Skip to main content

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": [ ... ] }, with links and meta beside it.
  • Field names are snake_case.
  • Times are ISO 8601, in UTC.
  • Every id is 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.

StatuscodeWhat it means
400invalid_jsonThe body is not valid JSON. Nothing was read.
401unauthenticatedThe key is missing, wrong, revoked or expired.
403forbiddenThe key's role cannot do this. A viewer key tried to write.
403feature_unavailableYour plan does not include API access.
403account_read_onlyThe account cannot make changes right now. Reads still work.
404not_foundNothing at that address, or it belongs to a project the key cannot reach.
409invalid_stateThe record cannot change in its current state.
409metric_in_useA test still uses this metric.
409audience_in_useA test still uses this audience.
422validation_failedA field is missing or wrong. errors lists each field and why.
429rate_limitedToo many calls. Wait for the time in Retry-After.
500server_errorA 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.

CallsLimit per key
Reads120 a minute
Creates, changes, deletes60 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​