Uploading & Managing Custom Integrations
Upload custom integrations through the Settings UI or API, and manage installed integrations
Custom Integration Scopes
Custom integrations exist at three scopes, all running through the same sandboxed execution path:
| Scope | Origin | Visibility | Who can upload |
|---|---|---|---|
First-party (org:scrydon:) | Baked into the Docker image | All organizations | Scrydon engineering (shipped with releases) |
Platform (org:platform:) | Platform admin uploads | All organizations | Platform administrators |
Organization (org:{orgId}:) | Org admin uploads | Single organization | Organization administrators |
All three scopes use the same sandboxed execution model. There are no trust tiers — every custom integration runs in an isolated Worker Thread regardless of origin.
Uploading via the Settings UI
Go to Settings > Platform > Integrations and select the Custom tab.
Direct URL: /settings/platform/integrations#custom
The Custom tab offers two doors:
- Git/OCI sync — register a pack source your CI/CD publishes to. Synced custom integrations appear in Add Integration under From your pack catalog — adding one installs and activates it immediately. When a new artifact is published, the card shows an Update badge; clicking it takes you to Settings → Platform → Packs where you can review workflow impact and apply the update. Installed integrations stay visible in the pack catalog group as Up to date. See Pack sources for setup, Pack version management for the update flow, and Packaging a custom integration in a pack for the pack authoring guide.
- Manual upload — upload a single
.bundle.tar.gzartifact directly (up to 50 MB).
The rest of these steps cover Manual upload.
Drag your .bundle.tar.gz file onto the upload area, or click Browse to select it from
your filesystem.
Maximum file size: 50 MB.
Click Upload. The platform validates the artifact and shows a review dialog summarizing the products, tools, and triggers it provides — keep it uploaded for later, or Upload & Install to activate it immediately.
The custom integration appears in the table. You can enable, disable, or delete it from the actions column.
Uploading via the API
Use a POST request with multipart form data:
curl -X POST https://your-instance.example.com/api/bundle-upload \
-F "bundle=@dist/my-vendor-1.0.0.bundle.tar.gz" \
-b "better-auth.session_token=<your-session-token>"The session token must belong to an organization administrator. Get it from your browser's DevTools (Application > Cookies).
Response
{
"vendorId": "my-vendor",
"version": "1.0.0",
"toolCount": 3,
"productCount": 1
}Error Responses
| Status | Meaning |
|---|---|
400 | Invalid bundle (manifest validation failed, file too large, bad format) |
401 | Not authenticated |
403 | Not an organization administrator |
500 | Storage or database error |
What Happens on Upload
The upload pipeline performs these steps:
Verifies the caller is an authenticated organization administrator.
Extracts the .tar.gz with security checks:
- No symlinks allowed
- No absolute paths
- No path traversal (
../) - Only allowed file extensions (
.js,.js.map,.json,.svg,.png,.jpg) - Maximum extracted size: 100 MB
Validates manifest.json against the ManifestSchema (Zod):
- Vendor ID format:
/^[a-z][a-z0-9-]*$/ - Tool IDs reference valid vendor prefixes
- Auth credential types are valid
- No duplicate product IDs
- Required fields are present
Writes the artifact to blob storage:
00_organization/integration-bundles/{vendorId}/{version}/bundle.tar.gzRegisters the integration with the vendor ID, version, manifest JSON, storage key, SHA-256 hash, scope, and uploader. (This is the org's installed-integration record — distinct from the pack catalog used by Git/OCI sync.)
Managing Installed Custom Integrations
Statuses
| Status | Meaning |
|---|---|
| Active | Custom integration is available for use in workflows |
| Disabled | Custom integration is installed but not available (manually disabled) |
| Pending Review | Custom integration uploaded but awaiting admin approval (if governance policy requires it) |
| Failed | Custom integration failed to load at runtime (check error message) |
Enable / Disable
Toggle a custom integration's status from the Custom tab. Disabling a custom integration removes it from the workflow editor but does not delete the stored files.
Delete
Deleting a custom integration removes the database record. The stored blob can be cleaned up separately.
Deleting an active custom integration will break any workflows that reference its tools. Disable it first and verify no workflows depend on it.
Versioning
Upload a new version of the same vendor to update it. The platform stores custom integrations by {vendorId}/{version}/, so multiple versions can coexist. The most recently uploaded active version is the one used at runtime.
Boot Sequence
When the platform starts:
- Scan first-party bundles — parse
manifest.jsonfrom/app/bundles/(Docker image) - Scan platform bundles — parse manifests from platform storage
- Scan org bundles — parse manifests from each organization's storage
- Populate Manifest Catalog — metadata only, no vendor code loaded
- Register SandboxActor type with the Dapr runtime
- Ready to serve — actors activate on demand (first tool execution)
Custom integration code is never loaded at boot time. The platform only reads manifest.json files (lightweight JSON parsing). Code loads lazily when a tool is first invoked, inside a sandboxed Worker Thread.
Organization Policies
Organization policies (configured by admins) govern custom integration behavior:
Execution Policy
{
"maxConcurrentExecutions": 10,
"maxTimeoutSeconds": 30,
"allowedCapabilities": ["llm", "stt", "tools"]
}Governance Policy
{
"allowedVendorScopes": ["scrydon", "platform"],
"allowOrgUploads": true,
"requireApprovalForUploads": false,
"blockedVendorIds": []
}allowOrgUploads— whether org admins can upload custom integrationsrequireApprovalForUploads— whether uploads need platform admin approvalblockedVendorIds— explicitly blocked vendor IDsallowedCapabilities— which capability types are permitted
Package Format
Every custom integration is packaged as a .tar.gz archive with this structure:
{vendorId}-{version}.bundle.tar.gz
├── manifest.json (vendor metadata, JSON Schemas, UI definitions)
├── dist/
│ └── index.js (compiled, minified ESM; all dependencies inlined)
├── meta/ (automatically generated)
│ ├── sbom.cdx.json (CycloneDX 1.6 SBOM)
│ └── metafile.json (esbuild dependency graph)
└── assets/ (optional)
└── icon.svg, icon.png (vendor/product icons)SBOM Generation
Every custom integration artifact automatically includes a CycloneDX 1.6 Software Bill of Materials (SBOM) at meta/sbom.cdx.json. The SBOM is generated during sdk-authoring integrations build with no additional tools or configuration required.
What's in the SBOM
The SBOM lists every NPM package that esbuild bundled into dist/index.js:
- Package name and version — exactly what's in the bundle
- License — SPDX license identifier from
package.json - Package URL (purl) — machine-readable identifier (
pkg:npm/zod@3.24.0) - Evidence — file paths proving the package is actually bundled (tree-shaken code is excluded)
The SBOM only includes packages that esbuild actually bundled. If a dependency was tree-shaken away, it won't appear — making the SBOM an accurate reflection of what ships in your bundle.
How it's generated
During sdk-authoring integrations build, the CLI:
- Runs esbuild with
metafile: trueto capture all input files - Extracts unique NPM packages from
node_modules/paths in the metafile - Reads each package's
package.jsonfor name, version, license, and description - Serializes everything to CycloneDX 1.6 JSON
- Writes the result to
meta/sbom.cdx.jsonalongside the esbuild metafile
No external tools or dependencies are required — the generation is built into the CLI.
How admins use it
After uploading a custom integration, the Dependencies tab in the vendor detail dialog shows the SBOM component count in the tab header and lists all auto-detected packages alongside declared dependencies. See Custom Integration Review Before Activation for the full review workflow.
Compliance
The CycloneDX format is widely supported by compliance tools. For environments that require SPDX, convert using:
cyclonedx-cli convert --input-file meta/sbom.cdx.json --output-file sbom.spdx.json --output-format spdxjsonThe raw esbuild dependency graph is also available at meta/metafile.json for debugging. You can visualize it at esbuild.github.io/analyze.
Custom Integration Review Before Activation
After a custom integration is uploaded, administrators can review its contents before enabling it for the organization. The vendor detail dialog provides a structured review experience across multiple tabs.
Capabilities Tab
Shows all products, tools, triggers, and runtime capabilities the custom integration provides. Administrators can enable or disable individual blocks from this tab. Intelligence capabilities (LLM, STT, TTS, Embedding) display model counts and support allowlist-mode policy configuration. See Capabilities for details.
Dependencies Tab
The Dependencies tab displays two complementary views of the custom integration's dependency tree:
- Declared dependencies — Hand-authored entries from
defineProduct({ dependencies: [...] })that include a human-readablereasonfor each dependency. - Auto-detected dependencies (SBOM) — The full set of NPM packages that esbuild bundled into
dist/index.js, extracted frommeta/sbom.cdx.json. Each entry shows the package name, version, license, and a machine-readable package URL (purl).
The tab header shows the total SBOM component count so administrators can quickly gauge the custom integration's dependency footprint. Organization dependency policies (risk levels, blocklists, post-install script blocking) are evaluated against both declared and auto-detected dependencies.
If a dependency is blocked by the organization's policy, it is flagged in the Dependencies tab with the reason (e.g., "Risk 'high' exceeds max 'medium'" or "Has post-install scripts"). Blocked dependencies prevent the custom integration from being activated until the policy is adjusted or the .bundle.tar.gz is rebuilt without the offending package.
Setup Tab
Shows the vendor's authentication configuration (OAuth, API Key, Bot Token, or None) and allows administrators to configure credentials before enabling the vendor's capabilities.
Storage Layout
First-party (baked into Docker image):
/app/bundles/{vendorId}/dist/index.js
Platform uploads:
{platformPrefix}/integration-bundles/{vendorId}/{version}/bundle.tar.gz
Org uploads:
{storageKeyPrefix}/00_organization/integration-bundles/{vendorId}/{version}/bundle.tar.gzTroubleshooting
Upload fails with "Invalid manifest"
Run the CLI validator locally to see detailed errors:
bunx @scrydon/sdk-authoring integrations test --level staticCommon issues:
- Vendor ID contains uppercase or special characters
- Tool ID doesn't start with the vendor ID prefix
- Missing required fields (
id,name,version,icon,auth) - Zod schema uses unsupported types (the extractor converts Zod → JSON Schema)
Custom integration status shows "Failed"
Check the error message in the Custom integrations table. Common causes:
dist/index.jshas a syntax error- Default export is not a
defineVendor()result - Missing
export defaultstatement - Runtime import fails (ensure all deps are bundled — esbuild should inline them)
Tool doesn't appear in the workflow editor
- Verify the custom integration status is Active in Settings > Platform > Integrations > Custom
- Verify the product is enabled for your organization in Settings > Platform > Integrations
- Check the block's
category— it must be"tools","blocks", or"triggers"to appear in the correct palette section