Skip to content

Versioning ​

The API is versioned by date train. One train is served today:

Version2026-11current

How a request picks its train ​

The key's own train. A key stores the train that was current when it was minted and answers on it, so an integration written against 2026-11 keeps getting the shapes it was written against after a later train opens. Today every key is on 2026-11, and a request sends nothing to say which train it wants.

Tightly-Version is the header a request will use to name a train other than the key's, once there is a second train to name. Until then the header is not read: sending it changes nothing, and there is no refusal for an unknown value. It is a declared optional parameter on every operation in the reference all the same, so a generated client offers you the argument and a client built today names its train from the start rather than being retrofitted with one. The changelog entry that opens a second train says how to name it.

The promise ​

A breaking change never lands on a published train. It opens a new one, and the old train keeps answering.

A change of this kindIs it breaking?What it does
A new operationNoShips to every train.
A new optional field on a responseNoShips to every train.
A new optional parameterNoShips to every train.
A field removed or renamedYesOpens a new train.
A field's type changedYesOpens a new train.
A refusal tightenedYesOpens a new train.
A default changedYesOpens a new train.

This is enforced by CI, not by good intentions: every change to the spec is diffed against the published artefact and a breaking diff to a published operation fails the build. That is also why there is no response-transform layer in the API: the contract is held by the gate rather than by code paths, so nothing accumulates behind it.

What the gate reads is the spec. A response typed field by field is covered by it: rename or remove one of those fields and the build fails. A handful of operations declare their success as a plain object and describe it with an example instead, and inside one of those the gate cannot tell a renamed key from an unchanged one. The three finance reports and the cash gate are in that set. There the changelog entry is the notice rather than the gate, which is the other reason every change carries one. Read the entry, and read fields by name.

What "published" means ​

An operation is part of the contract once it appears in the published spec. Anything not in that file is not part of the contract, whatever a route happens to answer today.

That distinction matters more than it sounds. Tightly has hundreds of internal routes; the public surface is the enumerated subset in the spec, and the enumeration is the promise. If you build against something you found by watching the app's own network traffic, nothing here applies to it.

Additive changes still get an entry ​

The changelog carries one entry per change, additive ones included, each naming the operation by the name a reader sees and saying what a caller has to do, which is usually nothing. A reader's question is "what is new", not only "what broke", and a release with no changelog entry cannot move the spec: the gate requires the entry.

Writing a client that survives a train ​

  • Read fields by name and ignore the ones you do not know. New optional fields arrive on every train, and a client that fails on an unexpected key fails on an additive release.
  • Branch on message.code, never on a status or on a sentence. Codes are stable within a train; sentences are written for people and can be improved.
  • Let the key carry the pin. A key takes its own train, which is exactly the pin most integrations want, and it needs nothing in your code to do it.

Upgrading ​

When a second train opens, the changelog entry names it and says what changed. To move:

  1. Read the entry.
  2. Mint a key after the train opens: a new key takes the current train. Run your suite against it in a staging environment.
  3. Replace the production key with one on the new train, with the old one stopping on the schedule you choose.

There is no forced migration date today. When one becomes necessary it will be announced in the changelog with the date on it, well ahead of it.

The machine-readable contract ​

Download OpenAPI 3.1 for 2026-11. It is generated from the code, its info.version is the train, and it is the same file our own drift job checks: download it, run the generator at that commit, and you get an identical file. Use it for codegen, for Postman, or to diff two trains yourself.

Tightly API, version 2026-11.