Versioning

How the Improve API is versioned, what counts as a breaking change, and how a deprecation is announced.

The public API is the datafile (GET /config/**) and the analytics endpoint (POST /api/log). It is version 1. Every response from it carries the version in a header:

Api-Version: 1

The other /api/* routes belong to the dashboard. They are not versioned and can change at any time, so don't integrate against them.

Stable paths

The current paths have no version prefix, and they are version 1. A breaking change ships under a new path prefix (/v2/config/...) next to the current paths, never in place. Both run side by side until the old version's sunset date.

What can change without notice

Additive changes can ship at any time and are not breaking:

  • new endpoints
  • new optional request fields
  • new response fields, headers or error codes
  • new values for params and other open-ended fields

Clients should ignore fields and headers they don't recognize, and treat an unknown error code like its HTTP status.

What counts as breaking

Removing or renaming an endpoint, a field or a header, making an optional field required, or changing the meaning or type of an existing field. These only ship in a new version.

Deprecation

When an endpoint is going away, its responses start sending two headers, and its operation is marked deprecated in the OpenAPI spec:

HeaderStandardValue
DeprecationRFC 9745When the endpoint was deprecated, e.g. @1767225600
SunsetRFC 8594When it stops working, e.g. Thu, 01 Jul 2027 00:00:00 GMT
LinkRFC 8288rel="deprecation" pointing at the migration guide

The sunset date is at least 6 months after the deprecation date. Deprecations are also listed in the change log.

Browser clients can read all of these cross-origin: they are listed in Access-Control-Expose-Headers.

Rate limit headers

The datafile's browser (Origin) path follows the IETF RateLimit header fields draft:

RateLimit-Policy: "browser";q=600;w=60

Every browser response says the quota (q, requests) per window (w, seconds) per IP. A 429 adds the current state and when to retry:

RateLimit: "browser";r=0;t=60
Retry-After: 60

Successful responses don't carry RateLimit: they are cached at the edge for 30 seconds and shared between clients, so a per-IP count on them would be wrong for most readers. Server requests with a token header are not rate limited and get no rate limit headers.

On this page