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 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 (apiKey is shown once — store it 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 },
"installSequence": 1,
"createdAt": "2026-05-31T10:00:00.000Z",
"apiKey": "sk_uat_...", // ← never shown again
"webhookSigningSecret": "whsec_..." // ← only on first install; read it back any time
}
} apiKey is shown exactly once in the install response.
If you lose it, the only recovery is to uninstall and reinstall — a new key is minted.
The webhookSigningSecret is different: it is returned
once on first install, but it is stable and your extension can read it
back at any time (see below), so losing it is recoverable without a reinstall.
Read your signing secret
Your extension can fetch the signing secret for its own installation, so it never has to depend on a human copying the value out of the dashboard at install time:
# Your extension reads the signing secret for its own installation.
# Authenticate with the installation's own API key.
curl https://api.usethrottle.dev/api/v1/installations/inst_01HZX.../webhook-secret \
-H "X-API-Key: sk_uat_..."
# ─── Response ─────────────────────────────────────────────────────────
{
"data": {
"installationId": "inst_01HZX...",
"installSequence": 2,
"endpointId": "we_01HZY...",
"url": "https://your-extension.example/webhooks/throttle",
"enabledEvents": ["order.created"],
"signingSecret": "whsec_..."
}
}
Authenticate with the installation's own API key (or its extension session token).
A dashboard user holding application:extensions:install can also read it. Any
other credential gets a 404. The route needs no
particular scope — the credential itself is the authorisation.
installSequence — which increments on every
(re)install while id stays put — to tell a reinstall
from a restart, and to know when to discard credentials you cached against the install.
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 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: { ... }, ... } } 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 ?? {}; 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.
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.”
{
"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"]
} type: "object", properties, and optional required. Use the advanced JSON mode for nested
objects, arrays, enums, or provider-specific validation.
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 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 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