Developer guide. For how the mechanism is built, see experimental-api-endpoints-plan.md.
Experimental endpoints live under /api/experimental/... and carry an explicit
stability contract:
Endpoints under
/api/experimental/...are unstable. They may change shape or be removed in any release — without notice and without a deprecation cycle. No semver promise attaches to this namespace.
Compare with /api/v1/..., which is the official, stable surface: its endpoints follow
semver, breaking changes require a major-version bump, and removals go through a
deprecation cycle.
We sometimes need to ship something to production before its API shape has settled:
- A new feature whose request/response contract is still being learned from real usage.
- An endpoint built for a specific client (e.g. our own UI) where we are not yet ready to commit to it as a public, supported interface.
- Something we want behind a clear "use at your own risk" sign while it matures.
Without an experimental lane, the only choices are bad ones: either freeze a design we
are not confident in onto /v1 (and then carry it forever, or break it with a major
bump), or keep the feature out of production until the API is perfect. Experimental
endpoints give a third path — ship now, iterate freely, commit later.
Use an experimental endpoint when:
- The request/response shape is likely to change as the feature matures.
- You want production traffic / real feedback before committing to a contract.
- The consumer is internal or opted-in, and can tolerate breaking changes between releases.
- You would otherwise be tempted to "just put it on
/v1for now and fix it later."
Do NOT use an experimental endpoint when:
- The endpoint is meant for third-party integrators who expect stability. They should not have to track breakage release-to-release.
- The contract is already well understood and unlikely to change — put it on
/v1. - You are tempted to use
experimentalas a permanent home to avoid the discipline of a stable API. It is a staging area, not a dumping ground (see below).
- It is not forever. Every experimental endpoint is expected to either be
elevated to
/v1once its contract stabilizes, or be removed if it does not pan out. An endpoint that sits inexperimentalindefinitely is a smell — it means a decision is overdue. - Callers are warned at runtime. Every experimental response carries
x-experimental: true— that is the supported signal to detect and surface in client code. Responses also carry aWarning: 299 ...header, kept only for legacy tooling that still reads it: RFC 9111 obsoletes theWarningheader and its warn codes, so do not build new client logic on it. - Typed clients must opt in. Experimental endpoints are declared in a separate
ExperimentalEndpointstype, not in the mainEndpointsunion, so the stable SDK surface stays honest. Consumers import them deliberately. - Use the typed API only. Register with
.get()/.post()/.put()/.delete()and AJV validators. Do not use.addRoute(): it is already deprecated across the whole API, and a namespace created to iterate on new contracts is the last place that should add to the legacy path.
stabilizes
experimental ───────────────▶ v1 (official, semver-stable)
(/api/experimental/x) (/api/v1/x)
│
│ does not pan out
▼
removed (no deprecation cycle needed)
Elevating to /v1:
- Confirm the contract is stable and you are ready to support it under semver.
- Add the endpoint under
/v1: register it onAPI.v1and declare its types in the appropriate*Endpointstype that is part of theEndpointsunion. - Optionally keep the experimental path forwarding to the new
/v1path for a transition window so existing callers are not broken on the day of promotion. - Remove the experimental declaration once the transition window closes.
Removing an experimental endpoint needs no deprecation cycle — that freedom is the whole point of the namespace. Still, log the removal and give a heads-up to any known consumers as a courtesy.
- No path lives in both unions. A duplicate key would silently attach a semver
obligation to a path advertised as unstable, so promotion means moving the declaration
to a stable
*Endpointstype, not leaving a copy behind. Nothing enforces this — the/experimental/path prefix keeps the two unions from overlapping in practice. - Generated API docs intentionally skip experimental endpoints. OpenAPI /
doc generation scans the
Endpointsunion, which experimental paths are deliberately kept out of, so they do not appear in public API docs. This is by design: an unstable surface should not be advertised as part of the documented contract. The runtimex-experimental/Warningheaders and this guide are how the namespace is surfaced instead. - Metrics are the promotion signal. Experimental traffic is recorded in the
REST API Prometheus metrics under
version=experimental, so real usage can inform whether an endpoint is ready to graduate to/v1or should be removed.
Experimental endpoints let you ship an API to production while its shape is still in
flux, without locking yourself into semver. They are a staging area, not a permanent
home: every one is expected to graduate to /v1 or be removed. If you need stability
guarantees, use /v1. If a third party will depend on it, use /v1.