Skip to main content
Polar uses date-based API versions to evolve its public API without changing the contract seen by clients pinned to a stable version.

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.
Bug fixes, performance improvements, and internal implementation changes do not need API versioning as long as the public contract remains unchanged.

Version lifecycle

Polar maintains three API versions on a quarterly release cadence: During the first week of January, April, July, and October:
  1. The Deprecated version is removed.
  2. Current becomes Deprecated.
  3. Next becomes Current and is frozen.
  4. A new Next version is created.
Releases are calendar-driven, even when Next contains no contract changes. Each version is therefore supported for approximately nine months: three months in each lifecycle stage. Schema-lock tests enforce the freeze on Deprecated and Current. Even backwards-compatible contract changes, such as adding an optional response field, must target Next.
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.
Versions use the 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:
Always use identity constants such as 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 the Polar-Version request header:
When the header is omitted, the request uses Current. Successful responses include the resolved version in the Polar-Version response header. A malformed, unknown, or removed version returns 404 Not Found.
Current changes every quarter. External clients should always set the header explicitly, either directly or by using a versioned SDK import.

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

The version 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:
Because markers refer to stable version constants, they do not need to change when versions move between Next, Current, and Deprecated.

Adding an endpoint

Use @version(starting_from=...) to introduce an endpoint in Next and every version after it:
The decorator must be below the router decorator, as shown above, so its metadata is present when FastAPI registers the route. It remains unchanged when 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 the display_name input field to name, keep the original endpoint as the fallback and introduce an override in Next:
The unversioned 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:
The marker remains unchanged as V2026_04 becomes Deprecated. Delete the endpoint when that version is removed.

Adding an output field

Use Version(starting_from=...) to introduce an output field in Next and every version after it:
The marker remains unchanged during rotations.

Removing an output field

Use Version(up_to=...) with the last version that exposes the field:
Delete the field when 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:
The same syntax applies to endpoints:
Version controls OpenAPI schema generation and output serialization. It does not change Pydantic validation: the field must still be valid on the model even in versions where it is hidden. To change an input schema, create a version-specific endpoint with a separate request model instead of using Version.

Cleaning up a removed version

During a rotation, update the lifecycle aliases and VERSIONS, 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 reads Polar-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.