Skip to main content
Three different things get versioned here, and they move independently. Keeping them apart is most of what this page is for. Minting a new operating point does not imply a new URL version. A repricing does not change a selection. A retune does not reprice anyone. Only a breaking change to the wire contract mints /v2.

URL versions are major-only

The only version identifiers that ever appear in a path are /v1, /v2, /v3. A minor or patch number never appears in a URL.
  • A breaking change is a major bump and gets a new URL version.
  • Patches and minor additions stay at the current URL version. A new optional response field, a new optional request field with a safe default, a new operating-point label, a bug fix — all ship at the existing /vN.

What counts as breaking

The test is the client’s, not ours: a change is breaking if a correct client written against the current contract stops working.

Nothing is served unversioned

The version segment is an explicit route. A path outside a known version prefix matches nothing and is rejected before any of our code runs. There is no unversioned route and no implicit “latest”. Clients pin a URL version explicitly, exactly as they pin an operating point.

Two rules that keep your client working

These are the client’s side of the bargain. A client that ignores them will break on a change this contract considers non-breaking — and that will be the client’s bug, not a broken promise.
1

Ignore response fields you do not recognise

New optional fields ship within /v1. Strict schema validation that rejects unknown fields will fail on the next additive change. The first one already designed is the usage block.
2

Do not exhaustively match the gate outcome or its reason

The gate outcome is an open, extensible enum. Today it takes two values — a select outcome (gate.engaged: true) and a stand down outcome (gate.engaged: false) — with the trigger carried in reason (engage:needle, standdown:high_drift, …).A third outcome, escalate, is planned: it signals that a caller declared a hard downstream capacity cap and neither a sufficient within-cap selection nor full pass-through was available. It will arrive additively — a new reason prefix and/or a new optional field — so a client that tolerates unrecognised prefixes will not break.

A version keeps serving until it is formally deprecated

Standing up /v2 is never, by itself, grounds for switching off /v1. Formal deprecation requires all of:
  1. A published deprecation notice naming the version, its replacement, and a sunset date.
  2. A migration path — what a caller must change, in writing.
  3. A notice period that has actually elapsed before removal, long enough that a pinned client is not broken by surprise.
Until every one of those is satisfied, the version keeps serving.
This commitment is load-bearing beyond ordinary API courtesy: published work cites an endpoint and an operating point, and those citations have to stay re-runnable. Retiring a URL version can invalidate a reproducibility promise attached to a published result, so it is not a decision that gets made on convenience.

Changes you should expect at /v1

Additively, within this URL version:
  • The usage block on the response — the metering receipt. Additive and optional, which is why it does not mint /v2.
  • mode: "shadow" as a third value for budget.mode.
  • An escalate gate outcome, as described above.
  • New operating-point labels, whenever the engine is retuned or a new engine revision is published.
None of these will break a client that follows the two rules above. That is why the rules are stated before the changes.