Ensemble Docs

Versions and lifecycle

Drafts and revisions, contract versions in the URL, the draft channel, the latest channel, deprecation, sunset and archiving.

There are two different kinds of version here, and keeping them apart is most of understanding this page.

KindWhere it livesChanges when
Contract versionIn the URL: /external/v1/...You decide to break the contract.
RevisionNowhere a caller can see itEvery time you publish.

A caller depends on the contract version. The revision is how you ship a fix without telling anybody.

Draft and published

Every API has at most one draft and any number of published revisions.

  • /external serves only published revisions, and only the newest one.
  • The draft is reachable on its own channel, covered below.
  • Editing a published API does not change it in place: you start a draft, which copies the published definition, and publish when you are ready.
  • Publishing promotes that draft to the next revision. Callers keep getting the previous one until the moment it lands.

Publishing re-validates the definition and checks that every target still resolves, so a draft written before a rule existed is caught at publish rather than at the first call. An API with no operations cannot be published.

The API list shows the draft when there is one and the newest published revision otherwise, so a row answers "what would I edit next" rather than "what is being served". Opening it shows both.

The URL is fixed once published

The slug and contract version are part of a URL callers depend on, so once anything has been published under them neither can be changed. The editor stops offering them, and the change is refused underneath in case something tries anyway.

To change the shape of the contract, publish a new contract version instead. That creates a separate API, with its own operations and its own grants, at a new URL:

/external/v1/benefits/...   still serving, untouched
/external/v2/benefits/...   the new contract

Both can run indefinitely. Nothing forces a caller off v1 until you deprecate and sunset it.

The draft channel

curl https://service.ensembleapp.dev/external/draft/benefits/coverage/E-4417 \
  -H 'x-api-key: sk_...'

The draft channel takes no version segment. An API has exactly one draft, so the slug identifies it. Everything else is the same: same authentication, same grants, same dispatch. Only the definition that answers differs.

It is a path segment rather than a header on purpose. A draft call shows up in an access log as a draft call, and nobody can switch a URL somebody believes is the published one into draft mode by adding a header.

If a slug carries several contract versions, the draft is ambiguous and the answer is 409 DRAFT_AMBIGUOUS naming the candidates. Pick one with a header:

curl https://service.ensembleapp.dev/external/draft/benefits/coverage/E-4417 \
  -H 'x-api-key: sk_...' \
  -H 'x-api-version: v2'

A response from the draft channel carries X-Api-Channel: draft, so a caller reading a response, or a log, can tell which definition answered.

A grant is held per API, and the draft shares its API with the published versions. A key granted on v1 reaches the draft by changing the URL, so a draft is only as private as the keys that can already call the API.

The latest channel

An API's contract version may be the literal latest instead of vN. On that channel, and only there, an operation may resolve its target to latest — the newest definition of the tool or agent, including an unpublished draft.

/external/latest/benefits/coverage/E-4417

latest is not a version, it is a promise not to make one. What a caller gets changes whenever somebody saves a tool draft: no publish, no review, nothing announced. That is only defensible for development, which is why the channel is named in the URL rather than hidden in a setting. A caller can see which one they are on.

Everything else is unchanged. A latest API is still published like any other, /external still serves only published API rows, and a key still needs a grant.

Moving an operation to a versioned API invalidates a latest target, which is what stops a definition granting itself draft access by being copied.

Status

Status/external answersResponse carries
activeNormallyX-Api-Version
deprecatedNormallyDeprecation: true, and Sunset if a date is set
sunset410 API_SUNSETSunset

Deprecation is a signal, not a restriction: the API keeps working, the generated document marks it, and callers who read response headers find out without being told. Sunset is the switch-off, and a sunset API needs a sunset date.

The usual sequence is to set deprecated with a Sunset date well ahead of time, leave it there long enough for callers to notice, then move to sunset.

Archiving

Archiving retires an API and releases its URL for another API to claim.

  • Its definition is kept for reference, read-only.
  • Grants on it survive, which is why the credentials screens flag a grant on a retired API rather than hiding it: it is a stale grant worth tidying.
  • There is no unarchive. Restoring one would have to re-claim a URL that may no longer be free, which needs a conversation about what happens when it cannot.

An archived API cannot be edited or published, because archiving released the slug and there is no URL left for a new revision to be served at.

On this page