Skip to content

API Stability & Deprecation Policy

This document is the compatibility contract for BRIDGEPORT’s HTTP API. It tells integrators what they can rely on, how breaking changes are versioned, how long a deprecated surface stays alive, and how to discover what a running instance speaks. Read it before you build anything against the API.

This policy covers the HTTP API — every endpoint under /api, plus the unauthenticated GET /openapi.json and GET /health endpoints. For these surfaces, the guarantees below apply.

Covered:

  • Request and response shapes (field names, types, nesting).
  • HTTP status codes and the standardized error envelope (code, message, field, hint, requestId).
  • The stable code enum on error responses (VALIDATION_ERROR, READONLY_FIELD, UNAUTHORIZED, FORBIDDEN_SCOPE, FORBIDDEN_ROLE, NOT_FOUND, CONFLICT, IDEMPOTENCY_KEY_REUSED, RATE_LIMITED, INTERNAL).
  • Authentication mechanics (Authorization: Bearer, the SSE ?token= query param, scope enforcement rules).
  • The machine-readable contract published at GET /openapi.json.

Not covered (these may change at any time, in any release, without a deprecation window):

  • The SQLite / Prisma schema and its migrations — an internal implementation detail. Never read the database directly; everything is exposed through the API.
  • The monitoring agent wire protocol between the agent and the backend.
  • Internal environment variables and configuration flags (covered separately by the Configuration Reference, which has its own compatibility notes).
  • The web UI — markup, routes, component behavior, and bundled assets are not an API. Build against the documented endpoints, not the pages.

BRIDGEPORT releases follow Semantic Versioning (MAJOR.MINOR.PATCH), and the wire format of the HTTP API is the thing semver protects:

  • MAJOR (e.g. 1.x2.0) — may contain breaking wire-format changes. The 2.0 release, for example, redesigned the service model (see Service → ServiceDeployment below) and was a deliberate, clean break.
  • MINOR (e.g. 2.02.1) — additive only. New endpoints, new optional fields, new error codes. Existing integrations keep working untouched.
  • PATCH (e.g. 2.0.12.0.2) — bug fixes and security fixes only. No new surface, no removed surface.

A change is breaking if a previously-valid client could observe a different, incompatible result. Breaking changes ship only in a major release.

ChangeBreaking?Allowed in
Removing or renaming a fieldBreakingMajor only
Changing a field’s type (e.g. stringobject)BreakingMajor only
Changing an endpoint’s HTTP status codeBreakingMajor only
Adding or tightening validation that rejects previously-accepted inputBreakingMajor only
Removing an endpointBreakingMajor only
Removing an enum value from a fieldBreakingMajor only
Adding a new optional field to a responseNon-breakingMinor / patch
Adding a new endpointNon-breakingMinor / patch
Adding a new enum value to a field documented as open/extensibleNon-breakingMinor / patch
Adding a new error codeNon-breakingMinor / patch

Clients must be tolerant readers: ignore unknown fields rather than rejecting them, and branch on the stable code enum — never on message substrings or, for error handling, on HTTP status alone. A response gaining a new optional field, or an error gaining a new code, is explicitly non-breaking and can happen in any minor release.


The single source of truth for the wire format is the OpenAPI 3.0.3 specification, generated at runtime via @fastify/swagger:

WhatPathAuth
Raw OpenAPI 3 spec (JSON)GET /openapi.jsonNo
Swagger UI (interactive)GET /api/docsNo
Per-release snapshotopenapi.json asset on each GitHub ReleaseNo

Both endpoints are unauthenticated so CI tools, code generators, and reverse-proxy probes can pull the spec without minting a token. info.version in the spec is sourced from the build’s APP_VERSION. Every tagged release also attaches the spec as an openapi.json asset, so you can diff the contract between versions without a running instance — this is what lets downstream consumers (e.g. the terraform-provider-bridgeport spec-diff job) flag new or changed operations.

Pin the snapshot. For reproducible integrations, fetch /openapi.json from the version you build against (or download the openapi.json asset from its release) and commit it to your repo. Diff it against a later instance’s spec to detect drift before upgrading:

Terminal window
# Snapshot the contract you build against
curl -s https://deploy.example.com/openapi.json > openapi.pinned.json
# Later, before upgrading, diff against the new instance
curl -s https://deploy.example.com/openapi.json > openapi.new.json
diff <(jq -S . openapi.pinned.json) <(jq -S . openapi.new.json)

Where the spec models a deprecated field or parameter, it carries deprecated: true so generated clients and linters can surface it automatically. Spec coverage of deprecations is best-effort and still expanding, though — treat the Current deprecations table below as the authoritative, complete list rather than relying on a spec diff alone.


The OpenAPI snapshot lets a consumer diff the declared contract. It can’t tell you whether a change quietly altered runtime behavior the spec doesn’t model — a default that shifted, a validation rule that tightened, a response that reshaped in a way the schema still nominally allows. To catch those, we run the real terraform-provider-bridgeport acceptance suite against every API-affecting PR.

The Provider Compatibility workflow (.github/workflows/provider-compat.yml):

  1. Builds the server image from the PR (same build args as the release/edge image).
  2. Checks out terraform-provider-bridgeport at its latest release tag (resolved dynamically, not pinned).
  3. Runs that provider’s acceptance suite (TF_ACC=1) against a disposable instance of the PR-built image, via the provider’s own scripts/acc-harness.sh — which spins up the container, waits for /health, mints an admin token, runs the Terraform acceptance tests, and tears the container down.

This is the runtime complement to the per-release openapi.json snapshot (see The canonical contract): the snapshot lets the provider repo run a spec-diff from its side; this job catches the behavioral breaks the spec diff can’t see. A green provider suite is a merge gate — a red suite fails the platform PR, and that is the enforcement behind this whole policy.

TriggerWhy
Pull requests to master that touch API-affecting pathsThe provider only talks to /api, so the job skips docs-, UI-, agent-, CLI-, and client-only PRs (paths-ignore).
Nightly (schedule, 04:00 UTC)Catches indirect drift no diff would flag on a given PR, and picks up newly-released provider versions.
On demand (workflow_dispatch)Takes an optional provider_ref input to test against an arbitrary ref — e.g. the provider’s master to validate an in-flight provider change against the current platform.

A red provider suite means one of two things, and they have very different responses:

  1. Accidental break (the common case). The change unintentionally altered a covered API surface (see Scope for what’s covered). Fix it here, in this PR, before merge — restore the compatible shape. The green suite is the gate; don’t merge around it.
  2. Intentional, policy-compliant break. The change is a deliberate breaking change shipping in a major release per Versioning & semver. It must not be force-merged past a red suite silently. It ships with: (a) a Deprecation entry where a window applies, (b) the required “API changes” release-notes entry (Changelog discipline), and (c) a provider-side follow-up issue filed before this PR merges, so terraform-provider-bridgeport is updated and re-released against the new contract. Only then do you coordinate the merge.
SymptomOwnerAction
Red because the change accidentally altered a covered surfacePlatform PR authorRestore the compatible shape in this PR; re-run until green.
Red because the change is an intended major breakPlatform PR author + provider maintainerDeprecate (where a window applies), document the “API changes” release-notes entry, file a provider follow-up issue before merge, then coordinate the merge + provider re-release.

We don’t break things without warning. When an API surface needs to go away, it is first deprecated, kept working for a guaranteed window, and only then removed.

A surface deprecated in a given release is supported through the remainder of that major series and is removed no earlier than the next major release.

Concretely: anything deprecated anywhere in 2.x keeps working for all of 2.x and is only eligible for removal in 3.0. This gives integrators the full lifetime of a major series to migrate.

Every deprecation is recorded in the two authoritative places below, and — where the spec models the affected field — surfaced in a third, machine-readable one:

  1. The Current deprecations table in this document — the complete, always-current list with replacements and removal targets. This is the canonical record.
  2. An “API changes → Deprecated” entry in the release notes — tells you when a surface was deprecated. See Changelog discipline below.
  3. deprecated: true in /openapi.json — machine-readable and picked up by code generators and OpenAPI linters, applied where the spec models the deprecated field. Coverage is best-effort and still expanding, so don’t rely on a spec diff alone to catch every deprecation.

If you’re integrating, the table above is your source of truth; the release notes tell you when the clock started, and the spec annotations help your tooling flag the cases it already covers.


Per-release notes live in the annotated git tag message for each release (rendered on the corresponding GitHub Release page), not in a flat CHANGELOG.md. Every release’s notes carry an explicit:

## API changes
### Added
- New `GET /api/...` endpoint for ...
### Deprecated
- `<field>` on `<endpoint>` — use `<replacement>` instead. Removal target: <major>.
### Removed
- `<surface>` (deprecated in <version>) is gone — migrate to `<replacement>`.

This section is required in every release’s notes and says None when there are no API-surface changes. It is the authoritative, dated record of additions, deprecations, and removals — distinct from feature/fix descriptions, which describe behavior, not contract.


A running instance exposes a small amount of build metadata through the unauthenticated health endpoint:

Terminal window
curl -s https://deploy.example.com/health
{
"status": "ok",
"timestamp": "2026-06-08T12:00:00.000Z",
"version": "2026060812-a1b2c3d",
"bundledAgentVersion": "2026053114-9f8e7d6",
"cliVersion": "2026052010-1a2b3c4"
}
  • version — the build stamp of the running backend, in the format YYYYMMDDHH-{7-char SHA}. Use it to identify exactly which build is deployed and to correlate with logs/Sentry releases.
  • bundledAgentVersion / cliVersion — the agent and CLI binaries bundled with this build. cliVersion is the build served by GET /api/downloads/cli; bundledAgentVersion is the agent image this instance deploys to your servers.

version is a build identifier, not a semantic version. The MAJOR.MINOR.PATCH semver lives only in the git release tag (e.g. v2.0.2); the server does not currently expose its semver or a runtime capability list. So you cannot read the semver from /health today.

For the wire contract, do not try to derive behavior from the build stamp — pin against the /openapi.json snapshot (The canonical contract) instead, and map the build stamp to a release by checking which tag it was cut from.

Roadmap (not a present capability): exposing the semantic version and a machine-readable capability list at runtime — to enable true client-side version/capability negotiation — is future work. Treat any such negotiation as not yet available; rely on the pinned spec for now.


The following surfaces are deprecated but still present. Per the deprecation window above, they remain available throughout 2.x and are scheduled for removal in 3.0.

SurfaceDeprecated inReplacementRemoval target
services[] flattened shape + service.server accessor2.0Read service.serviceDeployments[]3.0 (next major)

In 2.0 the service model was split: Service became a template (image, env, health, compose), and per-server runtime (container name, status, discovery, ports) moved to ServiceDeployment. For back-compat, responses still include a flattened services[] array (one row per deployment) and a service.server-style accessor that resolves to the first deployment’s server. New integrations should read service.serviceDeployments[] rather than the flattened shape.


The following surfaces were deprecated in 2.x and have now been removed. Integrations must migrate before upgrading to 3.0.

SurfaceDeprecated inRemoved inMigration
Sync response top-level success: boolean2.0 (#127)3.0 (#235)Read status instead

The sync endpoints (POST /config-files/:id/sync-all, POST /services/:id/sync-files, POST /servers/:serverId/sync-all-files) used to return a top-level success: boolean alongside status. That alias was removed in 3.0; read status === 'ok' instead. Note that status carries information the boolean couldn’t: a zero-target sync returns 200 with status: 'no_targets', which you should surface as a warning, not as a green success — it is distinct from 'ok'. The envelope is now:

{
"status": "partial",
"targetsAttempted": 3,
"targetsSucceeded": 2,
"targetsFailed": 1,
"results": [ ... ]
}

  • Found a breaking change that wasn’t announced? That’s a bug — open an issue on the GitHub issue tracker with the version from /health and a requestId from the affected response.
  • Need a deprecation window extended, or unsure how to migrate? Open an issue describing your integration; we’d rather hear about it before a major release than after.
  • Security-sensitive API behavior? Follow the Security Policy — do not open a public issue.