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

AxisWhere it livesCurrent valueCadence
API surfaceRequest path: /v1/op/{operation_id}v1Changes only on a surface-incompatible break
API contractinfo.version in the OpenAPI document2.0.0Tracks the API contract
Python clientgeopera on PyPI2.0.0SemVer, independent
TypeScript client@geopera/sdk on npm2.0.1SemVer, independent
CLIgeopera-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:

http
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:

json
{
	"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 → /v2 surface 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:

bash
# Python — install the published client (PyPI package: geopera)
pip install "geopera>=2.0.0,<3.0.0"
bash
# TypeScript — npm package: @geopera/sdk
npm install @geopera/sdk@^2.0.1
bash
# 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.

http
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 }
  1. Path version (/v1) — unchanged. The new operation is invoked at /v1/op/... exactly like every existing one. Adding an operation is additive, so /v1 does not move.
  2. OpenAPI info.version — bumped (e.g. 2.0.0 → 2.1.0) because the contract gained an operation.
  3. geopera (Python) — released as a minor bump (e.g. 2.0.0 → 2.1.0); the new operation appears as a typed method.
  4. @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.
  5. geopera-cli — picks up the new operation transparently the next time it depends on the updated geopera SDK; 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:

Guidance

  • Build against /v1 and 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.