The problem
Picture an agent that picked your endpoint off the Bazaar two weeks ago. It paid 0.005 USDC and hard-coded the JSON shape into its planning loop. Now it runs that call at 3am on a cron. No human reads the response. No human will ever read your changelog.
Multiply by a hundred agents. Some are LLMs routing through MCP catalogs. Some are scripts written by another LLM six weeks ago. The original author may not exist anymore. And you want to rename a field.
You can't.
So we generally don't version
Across our 9 clusters and ~30 paid endpoints, we have exactly zero /v2/ paths in production. That's deliberate.
Versioning forces the caller to do work. A new URL means a new entry in their config and a new code path on their side. Agents don't do that work. They keep calling /v1 until /v1 returns a 404, at which point they fail silently and stop paying you.
So the default rule across the portfolio is simple. Extend, never break.
What "extend" means in practice
- Adding a field to a response? Fine. Old clients ignore it.
- Adding an optional input parameter with a sensible default? Fine.
- Renaming a field? No. Add the new one alongside the old one. Both ship in the response.
- Changing a field's type from string to object? No. Add a sibling field with the new shape.
- Removing a field? Almost never. If you absolutely must, set it to
nullfor six months first.
The response payload grows over time. That's the price.
When we do version the URL
URL-path versioning happens twice a year at most, and only when the contract truly can't be extended. Two cases come up:
1. The pricing model changes. secrets-exposure-check at 0.01 USDC per call is a different product from a hypothetical 0.001 USDC per check bulk endpoint. New product, new path.
2. The auth model changes. If we ever moved an endpoint from x402 to a different settlement layer, that's a different API and deserves a different URL.
The pattern looks like this:
GET /api/check-secret # original, still live GET /v2/check-secret/batch # new shape, new price, new path
The /v2 doesn't replace /api. It runs alongside. Forever, ideally.
The deprecation window we actually use
Sometimes a field has to die. A header gets renamed by an upstream provider. A schema element becomes nonsensical after a refactor. When that happens:
- Month 0: ship the new field. Old field still populated.
- Month 0–3: both fields in every response. The deprecation note goes in the
X-Deprecated-Fieldsheader and in the/discoverJSON for the endpoint. - Month 3–6: old field still present but always
null. New field is the source of truth. - Month 6+: consider removing the old field. Usually we don't.
Six months is roughly two LLM generations. By month six, most agents calling your endpoint are running on a model that wasn't released when the old field existed.
Why bother with the header? Because some agent frameworks (and some humans) do log unexpected response headers. It's the only place a "this is going away" signal has a real chance of being seen.
What this costs
Response payloads bloat. A /discover endpoint that started as eight fields is now sixteen, half of them ghosts from older schemas. Some haven't been read by any caller in a year. We pay maybe 50 bytes per response in dead schema weight.
Worth it. The agent who hard-coded result.matches[0].severity in August still gets a working response in April. That agent is paying us 0.005 USDC every time it runs. We're not breaking that for a cleaner JSON shape.
The TL;DR for endpoint authors
Before you ship a breaking change, ask one question. Is there any agent on any LLM anywhere that already calls this with the old shape? If you can't prove the answer is no, extend instead.