Docs
Versioning
Two version lines that move independently — the protocol's formats, and an implementation's HTTP surface — and which one to pin.
Two lines, on purpose
| Looks like | Changes when | Header | |
|---|---|---|---|
| Protocol version | 0.2.0, and 0.1.0 for objects without grammar | A format changes — a field, a state, a signed payload | Path-Protocol-Version |
| API version | 2026-10-04.genesis | An implementation's HTTP surface changes | Path-Version |
Changelog headings (0.2.0, 0.3.0, 0.4.0) name a release note. They are not the protocol_version inside a signed object.
A protocol change almost always forces an API change. The reverse is not true: renaming a query parameter is an implementation concern and leaves every signed object untouched.
Collapsing the two would mean bumping the specification every time an implementation tidied a route — which makes a stable protocol look unstable, and trains integrators to ignore version numbers entirely.
API versions
Dated releases inside a named train, in the Stripe style.
The train is a long-lived line. Within it, each dated release is a snapshot of the HTTP surface. New dates are added; old ones keep working.
Pinning
Path-Version: 2026-10-04.genesisnew PathClient({ baseUrl, apiVersion: '2026-10-04.genesis' });An unpinned request gets the latest version of the default train, and the response says so:
Path-Version: 2026-10-04.genesis
Path-Version-Pinned: falseThat is fine while exploring and wrong in production. Unpinned means a shape can change under you, and you find out from a customer rather than from a test.
Path-Version-Pinned: false means the client did not pin a version. Pin at construction
and change the constant deliberately.
Path alias
The reference implementation also accepts the train in the path, for callers whose proxies strip unfamiliar headers:
Same routers. /api/path/v1 is this implementation's mount. The protocol does not require it. A client of another operator uses the absolute URLs in that operator's discovery endpoints, and GET /path/{reference} for a scanned code.
How a change reaches you
A pin selects the body this implementation writes. Signed objects are served as signed. A caller
pinned to 2026-09-09.genesis writes the 0.3 bodies. It does not receive a grammar-2 object
rewritten into the older shape.
Adopting a newer date is changing one string and running your tests.
What a pin writes
Path-Version | protocol_version | amount | Signed bytes |
|---|---|---|---|
2026-09-09.genesis | 0.1.0 | A decimal string, beside currency | A resolver answer includes standing. A receipt includes status, reason_code and supersedes. At issuance reason_code and supersedes are null. A failed receipt requires a reason_code from the closed registry. |
2026-10-04.genesis | 0.2.0 | An Amount: value, asset_type, asset_code | The object carries grammar: 2. |
There is no object signed protocol_version 0.2.0 under 2026-09-09.genesis.
A verifier that replays the bytes it was given checks those bytes. A verifier that rebuilds the payload from its own model includes every field the row above names, including a null.
Protocol versions
Semantic versioning over the formats.
| Change | Bump |
|---|---|
| A new optional field | Patch |
| A new object, a new URI kind, a new profile | Minor |
| A field removed or re-typed; a signature payload changed | Major |
Every signed envelope carries the version it was produced under.
{
"type": "path.receipt",
"grammar": 2,
"protocol_version": "0.2.0",
"…": "…"
}A verifier never has to guess. This is not decoration: a signature covers the canonical bytes of the payload, so a verifier that assumed the wrong version would canonicalise differently and reject a valid envelope.
Reserved fields
RESERVED fields are present on the object and unused in this version.
"route": nullAn implementation MUST carry reserved fields through untouched and MUST NOT reject an object because one is populated by a newer counterparty. Dropping unknown fields is the single most common way to break forward compatibility, and it fails silently.
What the draft does not promise
The formats may change before v1.0. The changelog records each change with what it breaks and what to do.
What will not change without a new protocol_version: the meaning of an error code, the bytes of a
signature payload, or the semantics of a status.