Extensions

Installing Extensions

An install pins a specific published version of an extension to an application, mints a dedicated API key (and optionally a webhook signing secret), and begins event delivery if a webhookUrl was declared.

Install an extension

Pass the versionId of the published version you want to install. Optionally pass config — a free-form JSON object that your extension reads at runtime. The install's environment is derived from the caller's API key or dashboard-selected X-Throttle-Environment-Id and cannot be overridden by the request body.

Install
# Install a published version of an extension into an application.
# Both the dashboard session token (Clerk JWT) and the Application ID header
# are required — installs are application-scoped.

curl -X POST https://api.usethrottle.dev/api/v1/extensions/ext_xxx/install \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "X-Throttle-Application-Id: <applicationId>" \
  -H "X-Throttle-Environment-Id: <workspace-environment-uuid>" \
  -H "content-type: application/json" \
  -d '{
    "versionId": "ver_xxx",
    "config": {
      "syncInterval": 60,
      "notifyOnNew": true
    }
  }'

# ─── Response (show once — store both values immediately) ────────────────────
{
  "data": {
    "id":          "inst_01HZX...",
    "extensionId": "ext_xxx",
    "versionId":   "ver_xxx",
    "status":      "active",
    "environmentId": "8a5f2d1e-3d2a-4d24-93a1-fd3f9e1f37f0",
    "environmentSlug": "uat",
    "environmentKind": "non_production",
    "providerEnvironment": "sandbox",
    "config":      { "syncInterval": 60, "notifyOnNew": true },
    "createdAt":   "2026-05-31T10:00:00.000Z",

    "apiKey":               "sk_uat_...",        // ← never shown again
    "webhookSigningSecret": "whsec_..."           // ← never shown again (only if webhookUrl declared)
  }
}
One-time secrets
apiKey and webhookSigningSecret are shown exactly once in the install response. If you lose either value, the only recovery is to uninstall and reinstall — a new key and signing secret will be generated.

Scope and event consent

When you install an extension, you implicitly consent to its declared scopes and event subscriptions. The installer must hold the extensions:install scope (API key) or the application:extensions:install RBAC permission (dashboard user). The issued API key is scoped exactly to the version's declared scopes.

If the extension later publishes a new version with additional scopes, upgrading to that version requires re-consent. The upgrade endpoint returns 409 new_scopes_require_consent listing the new scopes, and the caller must retry with acknowledgedNewScopes: true. See the Versioning guide.

Configuration

The config field is a free-form JSON object passed at install time and updateable any time thereafter. Use it for extension-specific settings such as API keys for third-party services, feature flags, or integration identifiers. Your extension reads it from the installation record; it is not embedded in the iframe URL or automatically included in the bridge session.

Update config
# Update the installation configuration after install.
curl -X PATCH https://api.usethrottle.dev/api/v1/installations/inst_xxx/config \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "X-Throttle-Application-Id: <applicationId>" \
  -H "content-type: application/json" \
  -d '{ "config": { "syncInterval": 120, "notifyOnNew": false } }'
# → { data: { id: "inst_xxx", config: { ... }, ... } }
Read config from an iframe
import { createBridge } from '@usethrottle/extension-bridge';

const bridge = createBridge({ targetOrigin: 'https://app.usethrottle.dev' });
const ctx = await bridge.ready;

// The bridge context gives you the installation id, not the config object.
// If your iframe needs runtime config, request extensions:read in the manifest
// and read the installation record through the scoped API client.
const installs = await bridge.api.get('/api/v1/installations') as {
  data: Array<{ id: string; config: Record<string, unknown> }>;
};
const install = installs.data.find((item) => item.id === ctx.installationId);
const config = install?.config ?? {};
Config is separate from the bridge context
bridge.ready resolves with user, workspace, application, environment, installation id, version, role, and scopes. It intentionally does not include config or configSchema. If a browser iframe needs config, give the extension an explicit read scope and fetch the installation record. Keep secrets that should never reach browser JavaScript on your extension backend instead.
Config is not the signing secret
Do not store the webhookSigningSecret in config — the config object may be readable by anyone with extensions:read scope. Store the signing secret in your own secrets manager.

Configuration schema

Extension authors can publish a configSchema to describe the settings an installer should provide. The dashboard authoring flow includes a field builder for simple string, number, and boolean settings plus an advanced JSON editor for custom JSON Schema. During install and on the management page, Throttle uses this schema to render the configuration form. Empty schemas are treated as “no configurable options.”

configSchema
{
  "type": "object",
  "properties": {
    "apiKey": {
      "type": "string",
      "title": "Provider API key",
      "description": "Issued by the upstream provider"
    },
    "syncInterval": {
      "type": "number",
      "title": "Sync interval"
    },
    "notifyOnNew": {
      "type": "boolean",
      "title": "Notify on new records"
    }
  },
  "required": ["apiKey"]
}
Schema support
The builder emits a JSON Schema object with type: "object", properties, and optional required. Use the advanced JSON mode for nested objects, arrays, enums, or provider-specific validation.
Schema lifecycle
configSchema lives on the workspace catalog extension while you are editing it. Creating a version snapshots that schema into the immutable version record. Installs use the pinned version's schema to collect per-install config values. Publishing a new schema does not rewrite existing installs until they upgrade to a version that carries it.

Environment-scoped installs

Installs are per environment. A UAT install receives only UAT events and gets an API key for that environment; a production install receives only production events and gets a production-environment API key. The installs are completely independent — each has its own config, its own API key, and its own event delivery endpoint.

List and inspect installs

List installations
# List all installations for the current application + environment.
GET /api/v1/installations
  -H "Authorization: Bearer <dashboard-session>"
  -H "X-Throttle-Application-Id: <applicationId>"
  -H "X-Throttle-Environment-Id: <workspace-environment-uuid>"

# Response shape: paginated list
# {
#   "data": [ { "id": "inst_...", "extensionId": "ext_...", "status": "active", ... } ],
#   "meta": { "requestId": "...", "pagination": { "cursor": null, "hasMore": false } }
# }

Install status values: active, suspended. Suspended installs stop receiving events and their launch-token endpoint returns 409 installation_not_active. Delivery logs are accessible at GET /api/v1/installations/:id/deliveries.

Uninstall

Uninstalling removes the install row for the current application and environment. The extension catalog entry and other applications' installs are not affected. The API key and signing secret issued at install time are immediately revoked.

Uninstall
# Uninstall removes the active install for this application + environment.
# The extension catalog entry and other applications' installs are unaffected.
curl -X POST https://api.usethrottle.dev/api/v1/extensions/ext_xxx/uninstall \
  -H "Authorization: Bearer <dashboard-session>" \
  -H "X-Throttle-Application-Id: <applicationId>"
# → 204 No Content