carousell/rctp

Versioning

Status: Draft 0.4, pre-release. Sources: Draft 0.4 sections 4.5 and 16, binding section 10, SECURITY.md.

RCTP has three kinds of version number. The release says which code you have. The capability version says which protocol a gateway speaks. The UCP release says which version of the Universal Commerce Protocol RCTP builds on. They move for different reasons.

At a glance

What Current value Where you see it
Specification Draft 0.4 (adopted for v0.2, 27 October 2026) spec/rctp-draft-0.4.md
Release tag v0.2.0-draft.4 (the Starter profile of Draft 0.4) Git tags, CHANGELOG.md
TypeScript SDK @rctp/sdk 0.2.0-draft.4, npm dist-tag draft (dry run: not yet published) npm install @rctp/sdk@draft once published
Python SDK rctp 0.2.0a4, the PEP 440 form of the same release (dry run: not yet published) pip install --pre rctp once published
Go module tag packages/gateway-go/v0.2.0-draft.4 (created at release) go get github.com/carousell/rctp/packages/gateway-go
Conformance runner @rctp/conformance 0.2.0-draft.4 pnpm conformance
Capability version 2026-11-draft5 (Draft 0.5; v0.2.0-draft.4 speaks 2026-11-draft4) RCTP-Version header, /.well-known/ucp, every file in registries/
UCP release v2026-08-25 (UCP Ask at commit f2626c2 until it is released) ucp.version (2026-08-25) in every UCP profile
Term sets goods.small/1, vehicles/1 config.term_sets of dev.rctp.listing; the listing's term_set

Release numbers

Releases follow semantic versioning 0.x, tagged with the draft number: v0.MINOR.PATCH-draft.N.

  • 0.x means anything may change between minor releases. There is no stability promise before 1.0.
  • -draft.N names the specification draft the release implements. v0.2.0-draft.4 implements Draft 0.4; v0.1.0-draft.3 implemented Draft 0.3.
  • Each new specification draft gets its own release tag.
  • The SDKs, the gateway module and the conformance runner share one release number.

Capability version and capabilities

2026-11-draft5 names the protocol a gateway speaks: the year and month of the draft, and its number [4.5(3)]. It is the version field of every registry file and of every RCTP capability entry in a UCP profile.

RCTP defines two capabilities of its own, both standalone UCP capabilities under the dev.rctp.* namespace:

Capability Covers config in a gateway profile
dev.rctp.listing Listing Terms and the listing.* verbs term_sets: the term sets the gateway enforces
dev.rctp.deal Offers, acceptance, checkout and the deal record: offer.* and deal.* offer_expiry (default_seconds, max_seconds), offer_budget (per_agent, per_operator, window_hours)

Each seller verb's scope sits under one of them, such as dev.rctp.listing:create or dev.rctp.deal:offer_accept. Operators also use dev.rctp.operator (who they are) and dev.rctp.deal with config.webhook_url (where webhooks go) in their own profiles.

UCP profile discovery

A gateway publishes its UCP profile at /.well-known/ucp (binding 10):

curl -s http://localhost:8080/.well-known/ucp
{
  "ucp": {
    "version": "2026-08-25",
    "capabilities": {
      "dev.ucp.common.identity_linking": [
        {
          "version": "2026-08-25",
          "config": {
            "scopes": { "dev.rctp.deal:offer_accept": { "default_tier": "ask", "allowed_tiers": ["ask", "auto"] } }
          }
        }
      ],
      "dev.rctp.listing": [{ "version": "2026-11-draft5", "config": { "term_sets": ["goods.small/1"] } }],
      "dev.rctp.deal": [
        {
          "version": "2026-11-draft5",
          "config": {
            "offer_expiry": { "default_seconds": 172800, "max_seconds": 604800 },
            "offer_budget": { "per_agent": 3, "per_operator": 10, "window_hours": 24 }
          }
        }
      ]
    }
  },
  "keys": [{ "kty": "EC", "crv": "P-256", "kid": "…", "x": "…", "y": "…" }]
}

(Abridged: the real profile lists every scope, and each RCTP entry also names its spec and schema.) dev.ucp.common.identity_linking is a UCP capability, so its version is the UCP release, not the RCTP capability version. Read the scopes and tiers, the term sets and the limits from here rather than hard-coding them; the conformance runner does. GET /.well-known/oauth-protected-resource (RFC 9728) names the marketplace authorization server.

The RCTP-Version header

Clients MAY send the version they speak as an RFC 9651 sf-string (binding 10):

RCTP-Version: "2026-11-draft5"
  • The SDKs send it on every request.
  • A gateway that does not serve the version answers 400 with {"error": "unsupported_version", "message": "supported: …"} and no RCTP-Decision header. This check runs before anything else.
  • The header is optional. Without it, the gateway does not check the version.

The reference gateway on this branch serves 2026-11-draft5 only (versions in its configuration).

Support window and overlap

Draft 0.4 lets a deployment keep serving the previous capability version for at most 90 days after it starts serving the new one [4.5(3)]. The v0.2 gateway does not speak 2026-10-draft3: a deployment that must keep it runs a v0.1 gateway beside the v0.2 one for that window, then retires it. Security fixes land in every supported version during the window (SECURITY.md).

Version Supported
v0.2.0-draft.4 (Draft 0.4, 2026-11-draft4) Yes
v0.1.0-draft.3 (Draft 0.3, 2026-10-draft3) For at most 90 days after a deployment moves to 2026-11-draft4, then no
Earlier drafts No

Deprecation policy

  1. Announce every deprecation in CHANGELOG.md, with the date support ends.
  2. Overlap. Keep the deprecated version working for the full window: at most 90 days.
  3. Registries only grow within a major version. Adding a registry value needs editor review. Removing one needs a new major version [16] (GOVERNANCE.md).
  4. Experiments expire on their own. An x- extension has an end date of at most two drafts or six months, and is removed or promoted by then. See Extensions.
  5. Security fixes are not held back by the policy. They land in every supported version.

What counts as breaking

Change Breaking?
A new registry value (verb, reason, checkpoint, level, term set) No, but agents must handle an unknown value safely: an unknown verb is never allowed, and an unknown reason is a refusal
A registry value removed Yes: needs a new major version
A new required field in an object Yes
A new optional field in an object Treat it as breaking: schemas are closed (additionalProperties: false), so an older validator rejects the new field
A changed term set Yes: a new term set version (such as goods.small/2). A listing records the version it was approved under [6.2]
A wire-format change in the binding (headers, signature profile, challenge) Yes: new capability version

Source: docs/versioning.md