parcelpump
Sign in

Display

DOCS / API VERSIONING

API versioning policy

Documenting the rule from day one so consumers can build with confidence.

Promise

/v1 is permanent. parcelpump will never:

  1. Remove a /v1/... endpoint.
  2. Change an existing /v1/... endpoint's URL.
  3. Reshape a /v1/... response in a way that breaks existing consumers (renaming a field, changing its type, removing a field).
  4. Add a required request parameter to a /v1/... endpoint.
  5. Tighten validation on a /v1/... request (e.g., shrink an accepted enum, add a stricter regex).

What we can do within /v1 (additive only):

  • Add new endpoints under /v1/....
  • Add new optional request fields / query params.
  • Add new fields to response objects (consumers must ignore unknown fields — standard REST practice).
  • Add new entries to enums where the consumer is documented as treating unknown enum values as "other / unknown".
  • Improve internal performance, reliability, observability — none of which is observable to the contract.

If a change would break the rule, it goes into a new major version (/v2/...) instead.

What's versioned vs what isn't

Versioned (under /v1/...):

  • Every data endpoint: parcels, sources, search, findings, scrape-jobs, admin-concurrency, admin-sources/health, etc.

Unversioned (no prefix; describes the service not the contract):

  • /health — liveness check; same shape forever
  • /openapi.json, /docs — the spec itself; describes the latest released version by default
  • /llms.txt, /.well-known/ai-plugin.json — discovery
  • /tiles/manifest.json and /tiles/<layer>/<hash>.pmtiles — the PMTiles archive shape is dictated by the PMTiles format, not by us

When v2 ships

If/when a breaking change becomes necessary, the launch sequence is:

  1. Implement v2 alongside v1. Both available simultaneously at /v1/... and /v2/.... Same Lambda, same DB; different route handlers.
  2. Update the OpenAPI spec. /v2/openapi.json documents v2; /v1/openapi.json is added (the bare /openapi.json defaults to the latest).
  3. Announce the deprecation. Email every active consumer key, note in /llms.txt and the changelog, set Sunset and Deprecation HTTP headers on every v1 response.
  4. Wait at least 12 months. v1 stays fully functional during this window. New features land in v2 only.
  5. Sunset v1. v1 endpoints return 410 Gone with a body pointing at the v2 equivalent. They are NOT deleted from the registry — the response just becomes a redirect/error so old code fails loudly rather than silently producing wrong data.

Internal mechanics

  • Routes live under app.METHOD("/v1/...", handler) in src/api/server.ts. Adding a v2 route adds app.METHOD("/v2/...", handler) (often delegating to a shared internal function).
  • src/api/route-registry.ts carries the version-prefixed path of every route. Each entry's path begins with /v1/. v2 entries begin with /v2/.
  • The OpenAPI generator includes both versions. Swagger UI shows both grouped by tag.
  • docs/api-reference.md (generated) lists both versions side by side.
  • The MCP server defaults to v1; a future PR can add a per-tool apiVersion parameter when v2 ships.
  • farminggame/src/lib/parcelpump-client.ts uses /v1/... URLs hardcoded. When v2 ships, the client adds new methods (or a wrapping abstraction) — the existing methods stay v1 forever.

Consumer guidance

  • Always include the version in your URL. Don't try to be clever with content-negotiation or default-version behavior. URL versioning is debuggable and grep-able.
  • Treat unknown response fields as ignorable. If we add parcels[*].new_field in v1, your v1 client must not reject it.
  • Pin the SDK / generated client to a major version. If we publish @parcelpump/client@1.x, that line never gains breaking changes. v2 ships as @parcelpump/client@2.x and you opt in by upgrading.

Rate limits + uptime stay version-agnostic

Rate limits (per key) and uptime guarantees (none formally yet) apply across versions equally. The version prefix is about the shape of requests and responses, not the availability of the service.

Why URL-based versioning

Tradeoffs we considered:

  • URL (/v1/parcels/...) — chosen. Most explicit, debuggable via grep/curl, works trivially with CloudFront caching and Swagger UI groupings.
  • Header (Accept-Version: v1) — cleaner URLs but harder to debug, requires extra -H flag for ad-hoc curl, less obvious in logs, doesn't compose with browser <a href> links.
  • Query param (?api-version=1) — mixes versioning into the filter parameters; awkward in Swagger UI; bad UX for sharing URLs.
  • Subdomain (v1.api.parcelpump.io) — requires a separate CloudFront distribution per major version, separate ACM cert per hostname, extra DNS — meaningful operational tax.

URL versioning costs nothing extra and has no real downsides at parcelpump's scale.