Versioning
Geopera versions two things independently: the API surface itself, encoded in the request path as /v1/op/..., and each client package, which follows Semantic Versioning on its own release cadence. Understanding which axis a change lives on tells you whether you need to update your code, bump a dependency, or do nothing at all — the path version changes rarely and only for surface-level breaks, while the packages change often as the API evolves and as client ergonomics improve.
Two version axes
| Axis | Where it lives | Current value | Cadence |
|---|---|---|---|
| API surface | Request path: /v1/op/{operation_id} | v1 | Changes only on a surface-incompatible break |
| API contract | info.version in the OpenAPI document | 2.0.0 | Tracks the API contract |
| Python client | geopera on PyPI | 2.0.0 | SemVer, independent |
| TypeScript client | @geopera/sdk on npm | 2.0.1 | SemVer, independent |
| CLI | geopera-cli (console command geopera) | 0.1.0 (Beta) | SemVer, independent |
The path version and the package versions are deliberately decoupled. The /v1 segment is a coarse, long-lived contract marker. The package numbers move much faster — they can advance through patch and minor releases many times while /v1 stays put, because most contract changes are additive and do not break the surface.
The API surface version (/v1)
Every one of the 227 operations is invoked the same way:
POST /v1/op/{operation_id} HTTP/1.1
Host: api.geopera.com
Authorization: Bearer gpra_...
Content-Type: application/json
{ ...operation input... }The /v1 in that path is the API-surface version. It is the single most stable thing in the API. It does not appear as a header, a query parameter, or a body field — it is structural, baked into the URL of every request. See Operations for how operation IDs encode list/get/delete intent in the name (there are no REST verbs or path parameters).
What “breaking” means for the path version
The /v1 segment changes only for a surface-incompatible break — a change to the calling convention shared by all operations, such as:
- The request method or path shape (e.g. moving away from
POST /v1/op/{id}). - The authentication model (see Authentication).
- The error envelope (see Errors, which uses RFC 9457
problem+json). - The pagination or idempotency contract shared across operations (see Pagination and Idempotency).
If such a break ever ships, it arrives at a new path prefix (e.g. /v2/op/...) and /v1 continues to serve existing clients in parallel. You will never have an existing /v1 request silently change shape underneath you.
What does not bump the path version
Adding a new operation, adding an optional input field, adding a field to a response, or adding a new enum value are all additive changes. They are surfaced through a new OpenAPI document version and new package releases — they do not touch /v1. Your existing requests keep working unchanged.
The OpenAPI document version
The spec is an OpenAPI 3.1.0 document whose info.version is currently 2.0.0:
{
"openapi": "3.1.0",
"info": {
"title": "Geopera Operations",
"description": "Official API for the Geopera geospatial data platform.",
"version": "2.0.0"
}
}This 2.0.0 is the version of the contract — the catalogue of operations, their inputs, and their outputs. Note that info.version (2.0.0) is distinct from the OpenAPI dialect version (openapi: 3.1.0) and from the path version (/v1); all three are unrelated numbers that happen to live in the same document.
How the contract relates to the clients
The Python and TypeScript clients are fully typed against the API contract, so a new operation or field in the contract appears as a typed method or property in each client once you upgrade. When the contract gains or changes something, its info.version is bumped and the clients are re-released under their own SemVer numbers.
The client version numbers diverge because each package also carries client-only changes (bug fixes, ergonomics) that release on their own schedule. That is exactly why @geopera/sdk sits at 2.0.1 while geopera sits at 2.0.0 — the TypeScript package has shipped one patch beyond its Python sibling despite tracking the same contract. Both expose typed inputs, outputs, and errors (Problem / HTTPValidationError).
Package versions and SemVer
Each client package follows Semantic Versioning: MAJOR.MINOR.PATCH.
- MAJOR — a breaking change to the package’s own API (a renamed method, a changed signature, a dropped export), or adoption of a breaking contract change. Bumping a major may or may not coincide with a
/v1 → /v2surface break. - MINOR — backward-compatible additions, typically new operations or new optional fields flowing in from an updated contract.
- PATCH — backward-compatible fixes with no surface change (build fixes, typing corrections, dependency bumps).
A package major bump and a path version bump are independent events. The Python client reached 2.0.0 as its first public release; that major number reflects the package’s own line, not the /v1 surface. Conversely, an additive contract change that leaves /v1 untouched can still produce a minor package release on both clients.
Pin clients the way you pin any dependency:
# Python — install the published client (PyPI package: geopera)
pip install "geopera>=2.0.0,<3.0.0"# TypeScript — npm package: @geopera/sdk
npm install @geopera/sdk@^2.0.1# CLI — console command is `geopera`
pip install "geopera-cli>=0.1.0"The CLI is at 0.1.0 and is marked Beta (Development Status :: 4 - Beta). It is a thin auth and dispatch shell over the published geopera SDK — all real API calls go through the SDK’s AuthenticatedClient. Treat pre-1.0.0 CLI releases as still-stabilizing: minor bumps below 1.0.0 may carry breaking CLI changes per SemVer’s zero-version rule.
Worked example: tracking a contract change end to end
Suppose a new operation catalog.search.v2 is added to the API. Here is how each version axis responds.
POST /v1/op/catalog.search.v2 HTTP/1.1
Host: api.geopera.com
Authorization: Bearer gpra_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{ "collections": ["sentinel-2-l2a"], "limit": 50 }- Path version (
/v1) — unchanged. The new operation is invoked at/v1/op/...exactly like every existing one. Adding an operation is additive, so/v1does not move. - OpenAPI
info.version— bumped (e.g.2.0.0 → 2.1.0) because the contract gained an operation. geopera(Python) — released as a minor bump (e.g.2.0.0 → 2.1.0); the new operation appears as a typed method.@geopera/sdk(TypeScript) — released as a minor bump from its current line (e.g.2.0.1 → 2.1.0); the new operation appears as a typed method.geopera-cli— picks up the new operation transparently the next time it depends on the updatedgeoperaSDK; its own version moves only if the CLI surface itself changes.
Your existing code keeps working throughout. To use the new operation, you upgrade the relevant package — you never change your base URL or path prefix.
Changelogs
Each package maintains its own changelog; consult the one for the package you depend on:
- Python
geopera— changelog shipped with the PyPI package and thegeo-pera/geopera-pythonrepository. - TypeScript
@geopera/sdk— release notes in thegeo-pera/geopera-typescriptrepository and on npm. - CLI
geopera-cli— release notes in thegeo-pera/geopera-clirepository.
Guidance
- Build against
/v1and treat it as permanent for the life of your integration; a successor surface would ship at a new prefix, not replace it in place. - Pin client packages with a SemVer range that allows minors and patches but caps the major (
>=2.0.0,<3.0.0), then upgrade deliberately. - Watch the relevant package changelog rather than the spec’s
info.version— the changelog is where breaking-vs-additive is called out for your client. - Remember the three numbers are independent:
/v1(surface),2.0.0(contract /info.version), and your package’s SemVer can each move without forcing the others.