Skip to content

Stability

Since 1.0 there is one promise: an integration written against /api/v1 or the MCP tools keeps working, on every 1.x release. Versions follow semver: a patch fixes, a minor adds, and a break would be a major, served beside the old one rather than instead of it.

  • Every route and method under /api/v1, at the path it has now.
  • Inputs: every parameter and body property still accepted, with at least the values and sizes it accepted. Nothing optional becomes required.
  • Outputs: every property still there, with the type it had. Nothing that was always present becomes optional or null.
  • Scopes: no route needs more than it did. What works with a read key keeps working with one.
  • Error shape: { "error": { "code", "message", "detail"? } }, and the codes in the REST API table keep their meaning and status.
  • MCP tools: every tool, by name, with every argument it takes. An argument a tool doesn’t take is refused, since 1.8.0: before, it was dropped without a word, so a misspelled argument did nothing. That was the one change to the tools’ behaviour in 1.x; a call that sends only the arguments its schema lists is unaffected.
  • Rendition URLs: /a/{id} and /a/{id}/{transform}, with the transform keys in use today. Who they serve changed once, at 1.4: people who can see the asset, signed URLs, and public assets (0012).
  • New routes, new MCP tools, new optional inputs.
  • New properties in responses. Ignore properties you don’t know.
  • New values in an enum a response carries, such as a new asset state or type. Handle the ones you know and pass the rest through.
  • Error messages: they are for people. Match on code.
  • Tool and route descriptions, and the MCP server’s instructions.
  • Anything under /api/auth, which is better-auth’s own surface for signing in; the web app is its only client.

The frozen surface is kept as data in the source repository, contract/api-v1.json and contract/mcp-v1.json. A test compares the running spec with them, and CI compares every pull request with the base branch’s copy, so re-freezing can’t hide a break. Anything added is frozen with it, by a script that refuses when something breaks (0010).

Something that has to go is deprecated first, never removed in 1.x:

  1. The OpenAPI operation is marked deprecated: true, its description says what to use instead, and the release notes say so.
  2. Responses from it carry Deprecation: true and a Link to the replacement.
  3. It keeps working for at least twelve months and until the next major, and is removed there only.

Security fixes are the one exception: if keeping something working keeps a hole open, it closes, and the advisory says what changed.

The latest minor of the current major gets fixes; security fixes also go to the previous minor for three months after a new one ships. See SECURITY.md.