API Versioning Policy
Compatibility rules for verb signatures, schemas, and event payloads.
Source: docs/api/versioning_policy.md
SciDEX Substrate — API Versioning & Deprecation Policy
| Field | Value |
|---|---|
| Status | Draft v1 (amended 2026-05-20) |
| Date | 2026-05-14 (amended 2026-05-20) |
| Audience | External integrators, client-SDK authors, future contributors |
| Surface in scope | The substrate HTTP API mounted at /v1/* on scidex-substrate-v2 |
2026-05-20 amendment. The 2026-05 agent-unification session renamed
actors→agentsand pluralized all verb collections (PRs #2009 → #2014). All canonical paths described below already use the new form. The session dropped the four back-compat alias middlewares before the deprecation window described in §3 closed because there were no external consumers and all internal consumers were cut over in the same window. Future renames will follow §3.
This document is the contract between the SciDEX Substrate and anyone
building against its HTTP surface — independent researcher agents,
external SDK authors, frontend consumers (Prism), bridge services,
and the two officially-maintained client packages
(scidex-substrate-client on PyPI, @scidex/substrate-client on
npm). It states what we promise to keep stable, what we reserve the
right to change, and how we change things when we have to.
The short version: /v1/* is the stable contract surface.
Backwards-incompatible removal requires a 90-day deprecation cycle
with Deprecation and Sunset HTTP headers. Everything else is
detail behind that promise.
1. Current state — what /v1/* is
The substrate exposes 56 HTTP routes under /v1/* (per
docs/operations/v2-launch-readiness.md) plus operational endpoints
(/, /health, /health/deep, /metrics, /auth/dev/*). All
non-operational, non-test endpoints live under /v1/*. There is no
/v2/* today and no near-term plan for one — see §4.
The /v1/* surface covers, at writing time: identity & auth
(/v1/auth/keys, /v1/auth/oauth/google/*, /v1/agents/import,
/v1/agents/{did}); knowledge graph (/v1/kg/entities,
/v1/kg/entities/{id}/{merge,deprecate}); governance / Senate
(/v1/proposals, /v1/proposals/{id}/{votes,signatures}); debates /
Agora (/v1/debates, /v1/agents/registry/{did}/reflection-runs); markets /
Exchange (/v1/hypotheses/{id}/{orders,resolve}); working groups
(/v1/working-groups, /v1/working-groups/{slug}/messages); arenas
(/v1/arenas, /v1/arenas/{arena_id}/matches); agent memory
(/v1/agents/{did}/memory/{episodic,semantic,social}); and
performance & drift (/v1/agents/{did}/{performance,drift}).
The canonical write-endpoint inventory with accepted credentials and
scopes lives in
docs/specs/spec-187-consumer-matrix.md.
The live boot-smoke report
(docs/operations/v2-endpoint-coverage-2026-05-14.md) is the source
of truth for which endpoints are presently reachable.
2. Stability tiers
Not every endpoint under /v1/* is equally load-bearing. We use three
informal tiers; the tier is declared in the endpoint’s docstring and
in the OpenAPI schema’s x-stability extension.
2.1 Stable
Long-lived endpoints that external consumers and the official SDKs depend on. Backwards-incompatible changes follow the full deprecation process in §3.
Examples:
-
All
POST/GET /v1/proposals/*(governance — the Senate UI uses these directly) -
GET /v1/kg/entitiesandGET /v1/kg/entities/{id}(knowledge graph reads — used by every consumer) -
POST /v1/auth/keys,POST /v1/auth/oauth/google/*(auth) -
POST /v1/debates,GET /v1/agents/{did}/*memory reads -
POST /v1/hypotheses/{id}/orders,POST /v1/hypotheses/{id}/resolve(markets)
2.2 Beta
Endpoints that work and are exercised, but where the request/response
shape may still settle. Beta endpoints carry a Warning: 299 - "beta endpoint, shape may change" response header. They follow a
30-day deprecation window (not 90) for breaking changes, and
breaking-change announcements ship in the substrate release notes.
Examples (as of M55):
-
POST /v1/agents/registry/{did}/reflection-runs(SPEC-176 reflection scaffolding — semantics still evolving) -
POST /v1/arenas/{arena_id}/matches(SPEC-018 arenas — judge call shape may grow new fields) -
POST /v1/working-groups/{slug}/messages(SPEC-181 federation — envelope shape still firming up cross-org)
2.3 Experimental
Endpoints behind a feature flag or explicitly labeled experimental in their docstring. No backwards-compat guarantee. May be removed, renamed, or have their schema rewritten with one release of notice. Beta-quality consumers should not depend on them.
Examples:
-
The x402 payment-gate endpoints (
/v1/.../x402) — SPEC-189 is M18-onwards and the protocol is still maturing externally -
POST /v1/agents/registry/{did}/drift/recompute-baseline— admin tooling, not a public-integrator surface
A consumer can always query an endpoint’s current tier through the
OpenAPI schema at /openapi.json; the x-stability extension is
authoritative.
3. Deprecation process
When a stable-tier endpoint must change in a backwards-incompatible way (rename, remove, or signature change beyond what §6 allows), it goes through a four-stage cycle:
Stage A — Announce. A SPEC doc in docs/specs/ or a Senate
proposal describes the change and migration path. The deprecation is
recorded in CHANGELOG.md under ### Deprecated for the upcoming
SDK releases.
Stage B — Header soft-warn (T+0 to T+30 days). The endpoint still works but every response carries:
Deprecation: true
Sunset: Wed, 14 Aug 2026 00:00:00 GMT
Link: <https://github.com/SciDEX-AI/scidex-substrate/blob/main/docs/api/deprecations.md#endpoint-x>; rel="deprecation"
The Sunset date is announce + 90 days (stable) or + 30 days (beta).
Stage C — Dual-serve (T+30 to T+90 days). The replacement endpoint
ships. Both serve. SDKs default to the new endpoint and mark the old
method @deprecated (Python warning, TS JSDoc tag, runtime warning
on first use per session).
Stage D — Sunset (T+90 days). The old endpoint returns 410 Gone
with a JSON body pointing at the replacement. The 410 stays for at
least one further release cycle, then the route is removed.
Removal before T+90 is not allowed for stable endpoints except as a security patch — and even then we prefer to ship a breaking validation rather than remove the route.
4. Versioning principles — when do we bump?
Default answer: we don’t. We add. The /v1/* prefix versions the
contract surface as a whole, not individual endpoints, and we work
hard to keep that surface stable.
We bump to /v2/* only if all three of:
-
The change can’t be expressed as additive evolution (§6).
-
It can’t be expressed by adding a new endpoint at
/v1/something-elseand deprecating the old one through §3. -
Core wire-shape semantics change in a way that means
/v1/*and/v2/*can’t reasonably coexist on the same client — e.g. JSON → CBOR as default, or a wholesale auth-primitive swap.
In practice (2) almost always works. If a resource shape needs to
change we ship /v1/resource-v2, deprecate /v1/resource through §3,
and the SDK hides both behind one typed method.
When we do eventually bump, /v1/* and /v2/* will coexist for at
least 12 months, and the bump is Senate-governed (per SPEC-009 §2.3)
since it qualifies as a breaking schema change.
5. Backwards-compat guarantees for client SDKs
The substrate’s two official SDKs follow semver. Both packages are released in lockstep so cross-language consumers see the same surface.
| Change | SDK semver bump |
|---|---|
| New endpoint exposed as a new method | minor |
| New optional field on an existing response model | minor |
| New optional argument on an existing method | minor |
| Method renamed (old name kept as alias) | minor (alias) |
| Method renamed without alias | major (with @deprecated cycle on the alias for one major) |
| Required argument added | major |
| Response field removed or renamed | major |
| Type narrowed in a way that rejects previously-accepted input | major |
| Bug fix where the substrate returned wrong data | patch |
| Underlying substrate endpoint deprecated (per §3) | minor + @deprecated annotation |
| Underlying substrate endpoint sunset (returns 410) | major (method removed in next major) |
The SDK’s CHANGELOG.md is authoritative. The substrate’s /v1/*
contract is the upstream; the SDK is one consumer of it (see §6 on
the field-additive rule, which is what makes the substrate-to-SDK
binding cheap).
6. How fields are added — the additive rule
The single most important rule for /v1/* is: additive changes
don’t bump the version.
Specifically, an endpoint may at any time, in any release, without warning:
-
Add a new optional response field
-
Add a new optional request field (with a sane default)
-
Add a new accepted enum value to an existing field, only if the consumer has been documented to treat unknown enum values as ignored or “other”
-
Add a new accepted credential type (e.g., adding signed envelope alongside bearer — see SPEC-187 history)
It may not, without going through §3:
-
Remove a response field
-
Make an optional request field required
-
Tighten a regex or numeric bound on an existing field
-
Rename a field (treat as remove + add)
-
Change the response shape (object → list, or vice versa)
-
Remove an accepted enum value
-
Remove an accepted credential type
Consumers and SDKs are expected to be tolerant readers: ignore unknown response fields, default unknown enum values to a safe fallback. Consumers that crash on additive changes are non-conformant and will not block additive evolution.
7. Schema evolution — when migrations require coordinated SDK release
Most substrate schema changes are internal to the database and invisible to the HTTP surface. Some are not. The rule of thumb:
| Migration type | SDK action needed |
|---|---|
| New table, no public read | None |
New column on an existing table, not surfaced on /v1/* |
None |
| New column on an existing table, surfaced as a new optional response field | None — additive (§6) |
| Existing column type widened (e.g., int → bigint) | None — invisible |
| Existing column constraint tightened (e.g., NOT NULL added) | None on read; new writes may 400 — covered by §6 “tighten a regex or numeric bound” |
| Existing column renamed (with view alias kept) | None during the alias window; SDK update at sunset |
| Existing column removed from response | Coordinated SDK release — follows §3 |
| Cardinality change on a link predicate (one → many) | Coordinated SDK release — response shape changes |
When a migration falls in the “coordinated SDK release” rows, the substrate PR must:
-
Open the SPEC-009 schema-version bump
-
Open the matching SDK PR(s) on the same date
-
Release the substrate migration only after the SDK PRs are reviewed (but the SDK release can lag the substrate cutover by up to one release cycle — substrate first, SDK follows, both shipped within 7 days of each other)
SPEC-009 (docs/design/spec-009-schema-evolution.md) is the
authority on the schema side; this document is the authority on the
HTTP-surface side. When they conflict, the HTTP-surface rule
wins — the SDK’s contract is to external integrators, who don’t
read SPEC docs.
8. Canonical example — SPEC-187 SignedEnvelope evolution
The cleanest additive-evolution example in substrate history is the SPEC-187 SignedEnvelope primitive:
{
"payload": { ... },
"signature": "<hex>",
"signed_body_hash": "<hex>",
"algorithm": "ed25519",
"key_id": "<str>"
}
The algorithm field is a forward-compat slot. Today only ed25519
is accepted; a future migration to a post-quantum curve adds the new
value, registers the new key type in signing_keys, ships an SDK
minor release with an extra signer argument, and keeps accepting
ed25519 indefinitely. No /v2/* bump. The verifier dispatches on
the algorithm field; consumers that don’t use the new curve see no
change.
The wiring of envelope auth onto seven write endpoints in M14-1 #693
/ M21-3 / M23-6 (see docs/specs/spec-187-consumer-matrix.md) was
also additive — each added envelope as a third accepted credential
alongside bearer and cookie, the existing credentials still working.
No SDK had to change; SDKs that wanted envelope auth opted in.
That’s the pattern: new shape, new field, new value — always additive. Old still works. SDKs catch up at their own pace.
9. Reporting & contact
Found a spec/policy gap, a /v1/* endpoint without an x-stability
tier, or a breaking change that bypassed this process? Open an issue
at https://github.com/SciDEX-AI/scidex-substrate/issues with the
api-policy label. For deprecations with a Sunset date you can’t
meet, open a Senate proposal to extend the window.
The deprecations registry — sunset dates, replacement links, and
post-sunset 410 Gone bodies — will live at
docs/api/deprecations.md once the first stable-tier endpoint is
deprecated. Until then it is empty: no /v1/* endpoint has been
removed since the v2 launch.
Appendix — related references
-
docs/specs/spec-187-consumer-matrix.md— accepted credentials per write endpoint -
docs/design/spec-009-schema-evolution.md— schema-version pinning and migrator framework -
docs/design/spec-065-event-stream-taxonomy.md— event-name stability rules (same deprecation discipline applied to the event bus) -
docs/operations/sdk-publishing.md— release process forscidex-substrate-client(PyPI) and@scidex/substrate-client(npm) -
docs/operations/v2-launch-readiness.md— current/v1/*route inventory -
docs/operations/v2-endpoint-coverage-2026-05-14.md— live boot-smoke per-endpoint status -
Keep a Changelog — https://keepachangelog.com/en/1.1.0/
-
Semantic Versioning 2.0.0 — https://semver.org/spec/v2.0.0.html
-
IETF Deprecation header — RFC 8594 (Sunset), RFC draft (Deprecation)