When to use API versioning
Target the Next API version whenever a change affects the public API contract, including:- Adding, removing, or replacing an endpoint.
- Adding, removing, or changing a request or response field.
- Changing a field’s type, requiredness, validation, or enum values.
- Changing request parameters, response status codes, error responses, or authentication requirements.
Version lifecycle
Polar maintains three API versions on a quarterly release cadence:
During the first week of January, April, July, and October:
- The Deprecated version is removed.
- Current becomes Deprecated.
- Next becomes Current and is frozen.
- A new Next version is created.
API versioning is currently being rolled out. Until the first full rotation,
only Current and Next exist; there is no Deprecated version or
DEPRECATED_API_VERSION constant yet. The three-version lifecycle and
examples below describe the steady state after that rotation.YYYY-MM format, such as 2026-04.
Each concrete version has a stable identity constant for as long as it is
supported. Lifecycle constants are aliases that change during a rotation:
V2026_10 in version markers. Using
CURRENT_API_VERSION or NEXT_API_VERSION would change the meaning of the
marker at the next rotation.
Selecting an API version
Clients select a version with thePolar-Version request header:
Polar-Version response header. A malformed,
unknown, or removed version returns 404 Not Found.
OpenAPI schemas and SDKs
An OpenAPI schema is generated for every supported version and exposed at/YYYY-MM/openapi.json, for example /2026-04/openapi.json.
The Python and TypeScript SDKs ship the schemas and types for every supported API
version. The import path pins the client to that version and automatically sends
the corresponding header:
How to change the API
Theversion endpoint decorator and Version field marker define an inclusive
version range:
Version accepts the same range arguments. Omit starting_from when there is no
lower bound and omit up_to when there is no upper bound. At least one bound is
required. When both are provided, the version is available when:
Adding an endpoint
Use@version(starting_from=...) to introduce an endpoint in Next and every
version after it:
V2026_10 becomes Current and then Deprecated.
Changing an existing endpoint
Request schema changes require separate Pydantic models and endpoint implementations. For example, to rename thedisplay_name input field to name,
keep the original endpoint as the fallback and introduce an override in Next:
update endpoint serves versions that do not match a
version-specific override. From V2026_10 onward, update_v2026_10 takes
precedence. The router’s built-in name="update" argument preserves the route
name, so both versioned OpenAPI documents expose the operation ID
customers:update and SDKs generate polar.customers.update.
Delete the fallback endpoint and its legacy schema when no supported version
predates V2026_10. Keep the new implementation and marker until V2026_10
itself is removed; then remove the decorator and rename the Python function to
update. Use the same pattern for response or behavior changes that cannot be
expressed with a field marker.
Removing an endpoint
Use@version(up_to=...) with the last version that contains the endpoint:
V2026_04 becomes Deprecated. Delete the
endpoint when that version is removed.
Adding an output field
UseVersion(starting_from=...) to introduce an output field in Next and every
version after it:
Removing an output field
UseVersion(up_to=...) with the last version that exposes the field:
V2026_04 is removed.
Limiting availability to a bounded range
Provide both bounds when an endpoint or field exists only during part of the API history:Cleaning up a removed version
During a rotation, update the lifecycle aliases andVERSIONS, then regenerate
every supported OpenAPI schema and SDK and run the schema-lock tests. Version
markers do not move merely because a version changes lifecycle stage.
Also delete an unversioned fallback once every supported version matches its
version-specific override. Since the fallback has no boundary marker of its own,
look for these route pairs when removing the last version before an override’s
starting_from boundary.
When removing the oldest supported version, find every reference to its identity
constant. Since no remaining version can be older than it, cleanup follows three
rules:
After resolving those references, delete the identity constant. Any missed
imports will fail immediately during type checking or test collection.
How it works under the hood
The versioning middleware readsPolar-Version, defaults it to
CURRENT_API_VERSION, rejects unsupported values, and stores the selected
version in the request context. It also adds the selected version to the response
headers.
Routes without @version are fallbacks for every supported version. A matching
versioned route with the same path and HTTP methods takes precedence. At startup,
overlapping versioned ranges are rejected if they match the same supported
version. The router decorator’s built-in name argument can override the route
name used to generate the OpenAPI operation ID and SDK method name.
For fields, Version applies the same range comparison when serializing Pydantic
models and generating JSON Schema. Finally, the application filters routes and
fields for each supported version to generate the versioned OpenAPI documents
used by the SDK generator and schema-lock tests.
See the API versioning design document
for the rationale, lifecycle timeline, and SDK release strategy.
