Skip to main content
Phosra pins versions at four layers — the API path, the OCSS spec version, dated Annex B editions, and the SDK semver. Each is stable and additive, so code you ship today keeps working.
Runnable. Every version string and status below was captured live from https://phosra-api-sandbox-production.up.railway.app on 2026-07-06.

The four layers

LayerWhere you see itToday’s valueChanges when
API versionURL path /api/v1/…v1A breaking API change → a new path (/api/v2); v1 keeps serving
OCSS spec versionocss_version in the trust list; OCSS-Spec-Version request headerOCSS-v1.0-preThe protocol itself revs
Annex B edition/api/v1/annex-b/editions/{n}(pre-Edition-1)The steward freezes a new numbered edition
SDKyour package.jsonsemver, e.g. @phosra/sdk@0.1.0Normal library releases; pin exact

API version — it is in the path

The major version is the first path segment. Everything under /api/v1 shares one contract, and additions inside v1 are backward-compatible. A future breaking change gets a new path — v1 is never mutated under you.
curl
BASE=https://phosra-api-sandbox-production.up.railway.app
curl -s -o /dev/null -w "v1 → %{http_code}\n" "$BASE/api/v1/platforms"   # → v1 → 200
curl -s -o /dev/null -w "v2 → %{http_code}\n" "$BASE/api/v2/platforms"   # → v2 → 404 (no v2 yet)
What “backward-compatible additions” means inside v1: we may add new endpoints, new optional request fields, and new response fields or enum values. Write clients that ignore unknown fields and do not break on a new enum value — that is the whole forward-compatibility contract.

OCSS spec version

The protocol carried over the API has its own version, OCSS-v1.0-pre. You can read the census’s binding of it directly from the (unmetered, unauthenticated) trust list:
curl
BASE=https://phosra-api-sandbox-production.up.railway.app
curl -s "$BASE/.well-known/ocss/trust-list" \
  | python3 -c "import sys,json; d=json.loads(json.load(sys.stdin)['document']); \
print('ocss_version =', d['ocss_version']); print('schema_version =', d['schema_version'])"
Output
ocss_version = OCSS-v1.0-pre
schema_version = 1
On signed census writes, your client declares which spec it built against by setting the OCSS-Spec-Version request header (covered by your RFC 9421 signature). Send OCSS-v1.0-pre; the census pins its own side via ocss_version, so a mismatch is detectable rather than silent.
OCSS-Spec-Version is a request header you set on signed census calls — it is not echoed on ordinary product-plane responses. Do not poll for it on a GET; read ocss_version from the trust list instead.

Dated artifacts — Annex B editions

Governed content (the rule registry, statute mappings) is published as frozen, numbered Annex B editions. An edition is content-addressed and immutable: once edition N is cut, its bytes never change, and its ETag is the artifact hash. Pin to an edition number and you have pinned to exact bytes forever.
curl
BASE=https://phosra-api-sandbox-production.up.railway.app
curl -s -o /dev/null -w "edition 1 → %{http_code}\n" "$BASE/api/v1/annex-b/editions/1"
# → edition 1 → 404   (the sandbox census is pre-Edition-1; no editions frozen yet)
A 404 on an edition number means that edition is not yet frozen — every number returns 404 before Edition 1 is cut. It is a clean not-found, never a substitution to a nearby edition. Rule slugs reference their registry edition inline as ocss-rules/<edition>#<slug>, so a stored rule always names the exact edition it was written against.

Conditional reads and caching

Discovery artifacts (the trust list, profiles, editions) return a strong ETag and Cache-Control: public, max-age=300. Send the ETag back as If-None-Match to get an empty 304 when nothing moved — free, and it does not count against your rate limit.
curl
BASE=https://phosra-api-sandbox-production.up.railway.app
# 1. First read — capture the ETag
ETAG=$(curl -s -D - -o /dev/null "$BASE/.well-known/ocss/trust-list" \
  | tr -d '\r' | awk 'tolower($1)=="etag:"{print $2}')

# 2. Conditional read — 304 while unchanged, 200 + body when it moves
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "If-None-Match: $ETAG" "$BASE/.well-known/ocss/trust-list"
# → 304
The trust list also carries a monotonic issue serial so you can detect a change without diffing the body — a higher issue.number means the census re-issued:
curl
curl -s "$BASE/.well-known/ocss/trust-list" \
  | python3 -c "import sys,json; i=json.loads(json.load(sys.stdin)['document'])['issue']; \
print('issue #', i['number'], 'at', i['issued_at'])"
# e.g. → issue # 136 at 2026-07-06T06:44:43Z

SDK versioning — pin exact

The published SDKs follow semver. Pin the exact version you tested against and bump deliberately; do not float on a caret range in production.
PackageLatestPin
@phosra/sdk0.1.0"@phosra/sdk": "0.1.0"
@phosra/link0.1.2"@phosra/link": "0.1.2"
@phosra/mcp0.4.0"@phosra/mcp": "0.4.0"
@openchildsafety/ocss (protocol/signing)0.1.3"@openchildsafety/ocss": "0.1.3"
These packages are pre-1.0 (0.x). Under semver, a 0.x minor bump may carry breaking changes, so an exact pin — not ^0.1.0 — is the safe default until a 1.0.0 line ships. Read the changelog before you bump.
npm
# Pin exact — reproducible installs, no surprise breaks
npm install @phosra/sdk@0.1.0 @openchildsafety/ocss@0.1.3

Staying forward-compatible

Ignore unknown fields

New response fields and enum values arrive inside v1. Deserialize leniently; never fail on a field you did not expect.

Pin dated artifacts by number

Reference Annex B editions by number and verify the ETag. Frozen bytes never move under you.

Pin the SDK exact

Test against a specific version, pin it, and bump on purpose after reading the changelog.

Watch the issue serial

Poll the trust list with If-None-Match; act only when issue.number climbs.

Next steps

Deprecation & sunset policy

The lifecycle before anything can change — advance-notice windows, the Deprecation/Sunset headers, and how to migrate.

Changelog

What shipped, when — including SDK releases and platform conventions.

Forward compatibility (OCSS)

How the protocol itself stays additive across editions.