Extensions

Extension Versioning

Extension versions are immutable snapshots of a manifest. Published versions cannot be changed. Installs are always pinned to a specific version, so rolling out a change requires publishing a new version and upgrading each install explicitly.

Version lifecycle

A version starts as draft and becomes published when you publish it. Once published, the version record is frozen — its scopes, iframeUrl, webhookUrl, and eventSubscriptions never change.

stateDiagram-v2
  [*] --> draft: POST /extensions/:id/versions
  draft --> published: POST .../publish
  published --> [*]: (immutable — never changed)
Extension version lifecycle. Published versions are immutable.
Create version
# Create a new version (snapshots the current manifest)
curl -X POST https://api.usethrottle.dev/api/v1/extensions/ext_xxx/versions \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "content-type: application/json" \
  -d '{
    "version": "1.1.0",
    "notes": "Added customers:read scope for contact lookup feature"
  }'
# Response:
# {
#   "data": {
#     "id": "ver_new...",
#     "version": "1.1.0",
#     "status": "draft",
#     "scopes": ["orders:read", "customers:read"],
#     "notes": "...",
#     "createdAt": "..."
#   }
# }
Publish version
# Publish a draft version (makes it installable)
curl -X POST https://api.usethrottle.dev/api/v1/extensions/ext_xxx/versions/ver_new/publish \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "content-type: application/json" \
  -d '{}'
List versions
# List all versions for an extension
GET /api/v1/extensions/ext_xxx/versions
# Response: { data: { versions: [ { id, version, status, scopes, ... }, ... ] } }
Draft versions are not installable
Only published versions may be referenced in an install call. Attempting to install a draft version returns 422 draft_not_installable_in_live for production environment callers (and similarly for non-production environments). Publish the version first.

Installs are pinned

When an extension is installed, the install row records the exact versionId. Publishing a new version has no effect on existing installs — they continue running on the pinned version indefinitely. This provides stability: a breaking manifest change cannot affect installs that have not opted in to the upgrade.

The dashboard may surface an "upgrade available" indicator when a newer published version exists for an installed extension. The installer decides when and whether to upgrade.

Upgrading

Use POST /api/v1/installations/:id/upgrade to move an install to a different published version. The install's API key is updated to reflect the new version's scopes; the config is preserved.

Upgrade with consent
# Attempt to upgrade to a new version
curl -X POST https://api.usethrottle.dev/api/v1/installations/inst_xxx/upgrade \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "X-Throttle-Application-Id: <applicationId>" \
  -H "content-type: application/json" \
  -d '{ "targetVersionId": "ver_new..." }'

# If the new version requests scopes not in the current install:
# HTTP 409
# {
#   "error": {
#     "code": "new_scopes_require_consent",
#     "newScopes": ["customers:read"]
#   }
# }

# Retry with explicit consent:
curl -X POST https://api.usethrottle.dev/api/v1/installations/inst_xxx/upgrade \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "X-Throttle-Application-Id: <applicationId>" \
  -H "content-type: application/json" \
  -d '{
    "targetVersionId": "ver_new...",
    "acknowledgedNewScopes": true
  }'
# → { data: { id: "inst_xxx", versionId: "ver_new...", ... } }
  • If the new version's scopes are a subset of the current version's scopes (scope reduction), no consent is required and the upgrade proceeds immediately.
  • If the new version requests additional scopes, the endpoint returns 409 new_scopes_require_consent with the delta. Retry with acknowledgedNewScopes: true to confirm.
Re-consent gates production installs
The consent requirement protects against a malicious extension publisher silently acquiring new scopes. The installer must explicitly acknowledge each new scope — the 409 lists exactly which scopes are being added so the operator can make an informed decision.

Rollback

Rolling back to an older version uses the same upgrade endpoint — just target an earlier versionId. Because older versions have fewer or equal scopes, rollback typically does not require consent. After rollback, the install runs the older version's manifest (including its iframeUrland event subscriptions).

Roll back
# Roll back to an older published version by upgrading to it.
# "Rollback" and "upgrade" use the same endpoint — just target an earlier versionId.
# No consent required if the older version's scopes are a subset of the current scopes.
curl -X POST https://api.usethrottle.dev/api/v1/installations/inst_xxx/upgrade \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "X-Throttle-Application-Id: <applicationId>" \
  -H "content-type: application/json" \
  -d '{ "targetVersionId": "ver_old..." }'
Config is preserved across upgrades and rollbacks
The installation's config object is not changed by an upgrade or rollback. If the new version requires different config keys, update config separately via PATCH /api/v1/installations/:id/config.

Versioning strategy

  • Use semantic versioning (e.g. 1.0.0) — it is stored as a string, not parsed.
  • Additive changes (new UI features within the same scopes, bug fixes, updated iframeUrl) can be shipped as a new version without requiring install consent.
  • New scopes always require explicit upgrader consent.
  • Removing scopes does not require consent — it is a reduction.
  • Keep at least the previous published version available to allow rollback without republishing.

See also: Security model for how scope ceilings are enforced at the version level, and Installing extensions for the full install lifecycle.