Scrydon
Integrations

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:

ScopeOriginVisibilityWho can upload
First-party (org:scrydon:)Baked into the Docker imageAll organizationsScrydon engineering (shipped with releases)
Platform (org:platform:)Platform admin uploadsAll organizationsPlatform administrators
Organization (org:{orgId}:)Org admin uploadsSingle organizationOrganization 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.gz artifact 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

StatusMeaning
400Invalid bundle (manifest validation failed, file too large, bad format)
401Not authenticated
403Not an organization administrator
500Storage 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.gz

Registers 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

StatusMeaning
ActiveCustom integration is available for use in workflows
DisabledCustom integration is installed but not available (manually disabled)
Pending ReviewCustom integration uploaded but awaiting admin approval (if governance policy requires it)
FailedCustom 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:

  1. Scan first-party bundles — parse manifest.json from /app/bundles/ (Docker image)
  2. Scan platform bundles — parse manifests from platform storage
  3. Scan org bundles — parse manifests from each organization's storage
  4. Populate Manifest Catalog — metadata only, no vendor code loaded
  5. Register SandboxActor type with the Dapr runtime
  6. 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 integrations
  • requireApprovalForUploads — whether uploads need platform admin approval
  • blockedVendorIds — explicitly blocked vendor IDs
  • allowedCapabilities — 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:

  1. Runs esbuild with metafile: true to capture all input files
  2. Extracts unique NPM packages from node_modules/ paths in the metafile
  3. Reads each package's package.json for name, version, license, and description
  4. Serializes everything to CycloneDX 1.6 JSON
  5. Writes the result to meta/sbom.cdx.json alongside 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 spdxjson

The 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-readable reason for each dependency.
  • Auto-detected dependencies (SBOM) — The full set of NPM packages that esbuild bundled into dist/index.js, extracted from meta/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.gz

Troubleshooting

Upload fails with "Invalid manifest"

Run the CLI validator locally to see detailed errors:

bunx @scrydon/sdk-authoring integrations test --level static

Common 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.js has a syntax error
  • Default export is not a defineVendor() result
  • Missing export default statement
  • Runtime import fails (ensure all deps are bundled — esbuild should inline them)

Tool doesn't appear in the workflow editor

  1. Verify the custom integration status is Active in Settings > Platform > Integrations > Custom
  2. Verify the product is enabled for your organization in Settings > Platform > Integrations
  3. Check the block's category — it must be "tools", "blocks", or "triggers" to appear in the correct palette section
On this page

On this page