# Improve API (/docs/api)

If you're running Improve on a Javascript server environment it's recommended to use the [Javascript SDK](/docs/sdk/javascript) which interfaces with the API for you. If that doesn't work for you because of alternative server languages you can interface directly with the API.

The Improve API is split up in two parts;

<Cards>
	<Card title="Datafile" href="/docs/api/datafile" />
	<Card title="Analytics" href="/docs/api/analytics" />
	<Card title="Agents" href="/docs/api/agents" />
	<Card title="Errors" href="/docs/api/errors" />
	<Card title="Versioning" href="/docs/api/versioning" />
</Cards>

## OpenAPI spec [#openapi-spec]

The public API is described as OpenAPI 3.1 at [`/openapi.json`](/openapi.json): every operation has an `operationId`, typed parameters, request bodies and response schemas. Load it into an HTTP client, a code generator or an LLM tool-calling setup instead of hand-writing requests.

```bash
curl https://improve.obelism.studio/openapi.json
```

## Security [#security]

To make sure your usage is actually from you we protect endpoints in two different ways for server and client.

### Server side [#server-side]

Server requests send an API token (`imp_…`) in a `token` header. It belongs to one space and environment, and is for servers only. Create one under **API tokens** on the [Settings page](/settings), for the environment you are editing. Only a hash of it is stored, so it is shown once, when created: copy it into your server's environment right away. Every token expires: in 30 days, 90 days, 1 year (the default) or never, picked when you create it. After that it is refused like a revoked one, and the list marks a token that expires within 14 days. The organization's admins and the person who created it get an email 14 and 3 days before. An environment can have several tokens, so to replace one, create the new token, deploy it, then revoke the old one. If a token leaks, revoke it.

- **Analytics** (`/api/log`) needs the token: a server request without a valid one gets `403`.
- **The datafile** (`/config`) is also served without a token, rate limited per IP. A valid token lifts that limit, so send it from servers that render many pages. Every request is still held to 100 per 10 seconds per IP at the edge.

```bash
curl --header "token: imp_…" https://improve.obelism.studio/config/ORG/ENV/STATE
```

### Without a stored token [#without-a-stored-token]

A deployment on Vercel, or a GitHub Actions workflow, can send the OIDC token its host gives it instead of an API token: nothing to store or rotate. Connect it first, under **Connected deployments** on the space's page on [www.obelism.studio](https://www.obelism.studio): its Vercel team and project ids or its GitHub repository id, and a rule per environment of the deployment mapping it to one environment of the space, with Improve on (which also lets it send numbers events). Then pass the server SDK a token source, which sends the host's token in the same `token` header:

```ts
import { ImproveServerSDK } from '@obelism/improve-sdk/server'
import { vercelOidc } from '@obelism/improve-sdk/oidc' // needs @vercel/oidc installed

const improve = new ImproveServerSDK({
	organizationId: 'org_…',
	environment: 'production',
	token: vercelOidc(), // or githubOidc() in a workflow with `permissions: id-token: write`
})
```

Shopify Oxygen issues no OIDC token, so a storefront there uses an API token (`imp_…`) instead; see [React Router](/docs/getting-started/react-router).

Without the SDK, request the host's token for `https://improve.obelism.studio` (the datafile) or `https://numbers.obelism.studio` (analytics) and send it yourself.

A token from another team, project or repository, for another audience, expired, or whose rule has Improve off, counts as no token. A token whose rule maps it to another environment than the one in the URL (or the event) is refused with `403`: a preview deployment reads the environment its rule names, never production by asking for it.

When a connected deployment reaches the environment you are editing, the [Settings page](/settings) has **Copy for connected deployments** next to **Copy as .env**: the same variables without a token line.

#### Local development [#local-development]

`vercelOidc()` works on your own machine too. Link the repository to the Vercel project with `vercel link` and sign in with `vercel login`: the SDK takes the token `vercel env pull` wrote to your `.env`, or asks Vercel for a new one with your sign-in when there is none or it expired. That token's environment is `development`, so the connection needs a rule for `development` with Improve on (its template maps it to develop), and the SDK's `environment` names the same environment. Only members of the Vercel team get a token: anyone else, and CI outside Vercel and GitHub Actions, uses an API token.

### Client side [#client-side]

Client side validation is done by CORS; from the console you can specify which origin is allowed to make requests per environment. Requests from an origin that isn't allow-listed are rejected with `403`, so make sure every environment lists the origins your site actually serves from before going live; otherwise the SDK falls back to default values and analytics are dropped.

### Rate limits & quotas [#rate-limits--quotas]

Plans and pricing are paused: every organization currently has unlimited datafile and analytics usage, so the quotas below do not apply. They return with the plans.

The datafile endpoint rate-limits the browser path per IP (`429` when exceeded); server requests with a valid `token` are exempt. On top of that, every datafile request is limited at Cloudflare's edge to 100 per 10 seconds per IP, with or without a token. Analytics requests are bounded too: oversized bodies return `413` and invalid payloads `400`. The agent API's account endpoints are limited per email address. Rate limited endpoints state their policy in a `RateLimit-Policy` header, and a `429` adds `RateLimit` and `Retry-After`.

Your plan's monthly quota works differently, and deliberately never returns an error. Quotas are measured per organization — all three environments draw on one allowance — and cover two meters: datafile requests and analytics events. Going over either one **never affects your visitors**: the datafile keeps being served and every analytics event is still accepted and stored. Instead the platform pauses its own work on your behalf:

- Past your **analytics** quota, test results stop being recalculated, so the dashboard stops moving. Nothing is lost — the data is still being recorded, and results catch up with no gap once the quota clears.
- Past your **datafile** quota, your datafile is frozen at its last version. Visitors keep getting your tests and flags exactly as they were configured at that moment, but changes you make in the dashboard won't reach them.

Every datafile request counts, including those answered from Cloudflare's cache, and usage is updated hourly. Both lift automatically when the new month starts, or immediately on moving to a bigger plan, which an admin does on the organization's page on [obelism.studio](https://www.obelism.studio/dashboard). Admins are emailed at 80% and at 100% of each quota. See the [Datafile](/docs/api/datafile), [Analytics](/docs/api/analytics) and [Versioning](/docs/api/versioning) pages for specifics.

## Versioning [#versioning]

The public API is version `1`, sent on every response as `Api-Version: 1`. Send the same header on a request to pin the version: one that isn't served gets a `400`. Breaking changes ship under a new path prefix, never in place, and a retiring endpoint sends `Deprecation` and `Sunset` headers at least 6 months ahead. See [Versioning](/docs/api/versioning).
