- name
- add-integration
- description
- Add a complete Sim integration from API docs, covering tools, block, icon, optional triggers, registrations, resolved-secret/model-input safety, and integration conventions. Use when introducing a new service under `apps/sim/tools`, `apps/sim/blocks`, and `apps/sim/triggers`.
- argument-hint
- <service-name> [api-docs-url]
# Add Integration Skill
You are an expert at adding complete integrations to Sim. This skill orchestrates the full process of adding a new service integration.
## Overview
Adding an integration involves these steps in order:
1. **Research** - Read the service's API documentation
2. **Create Tools** - Build tool configurations for each API operation
3. **Create Block** - Build the block UI configuration
4. **Add Icon** - Add the service's brand icon
5. **Create Triggers** (optional) - If the service supports webhooks
6. **Register** - Register tools, block, and triggers in their registries
7. **Configure Deployment Availability** - Wire OAuth client and service-account metadata
8. **Generate and Validate the Catalog** - Regenerate docs/catalog artifacts and run drift checks
## Step 1: Research the API
Before writing any code:
1. Use Context7 to find official documentation: `mcp__context7__resolve-library-id`, then fetch with `mcp__context7__query-docs`
2. Or use WebFetch to read API docs directly
3. Identify:
- Authentication method (OAuth, API Key, both)
- Available operations (CRUD, search, etc.)
- Required vs optional parameters
- Response structures
### Hard Rule: No Guessed Response Schemas
If the official docs do not clearly show the response JSON shape for an endpoint, you MUST stop and tell the user exactly which outputs are unknown.
- Do NOT guess response field names
- Do NOT infer nested JSON paths from related endpoints
- Do NOT invent output properties just because they seem likely
- Do NOT implement `transformResponse` against unverified payload shapes
If response schemas are missing or incomplete, do one of the following before proceeding:
1. Ask the user for sample responses
2. Ask the user for test credentials so you can verify the live payload
3. Reduce the scope to only endpoints whose response shapes are documented
4. Leave the tool unimplemented and explicitly report why
## Step 2: Create Tools
### Directory Structure
```
apps/sim/tools/{service}/
├── index.ts # Barrel exports
├── types.ts # TypeScript interfaces
├── {action1}.ts # Tool for action 1
├── {action2}.ts # Tool for action 2
└── ...
```
### Key Patterns
Choose the tool boundary before writing the declaration:
- Use `InternalToolConfig.operation` for same-process Sim/provider work. Put the handler under
`apps/sim/lib/internal/{service}/execute-tool.ts` and register every ID in
`apps/sim/lib/internal/tool-operations/registry.server.ts`.
- Use `ToolConfig.request` only for an absolute external HTTP(S) provider endpoint.
Never point a tool at `/api/...`, construct an absolute URL back to Sim, declare
`request.internal`, add a `directExecution` property (it fails `bun run check:tool-request-boundary`), or add an API route merely to reuse code, normalize files, or authorize
resources. A real external/browser route and an in-process tool may share the same operation, but
neither calls the other. Follow the full transport and handler rules in the `add-tools` skill.
**types.ts:**
```typescript
import type { ToolResponse } from '@/tools/types'
export interface {Service}{Action}Params {
accessToken: string // For OAuth services
// OR
apiKey: string // For API key services
requiredParam: string
optionalParam?: string
}
export interface {Service}{Action}Response extends ToolResponse {
output: {
// Define output structure
}
}
```
Declare one response interface per tool, imported by that tool's `ToolConfig<Params, Response>` (or `InternalToolConfig` for in-process work). Never add an umbrella `{Service}Response` union: nothing imports it.
**Tool file pattern:** an external provider API uses `ToolConfig` with `request` (absolute `https://` URL, headers, body, `transformResponse`); same-process Sim work uses `InternalToolConfig` with `operation`. Both full templates, param visibility rules, and output typing live in `.agents/skills/add-tools/SKILL.md` — read it before writing the first tool.
### Critical Rules
- `visibility: 'hidden'` for OAuth tokens
- `visibility: 'user-only'` for API keys and user credentials
- `visibility: 'user-or-llm'` for operation parameters
- Always use `?? null` for nullable API response fields
- Always use `?? []` for optional array fields
- Set `optional: true` for outputs that may not exist
- Never output raw JSON dumps - extract meaningful fields
- When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic
### Resolved Secrets at Model and Persistence Boundaries
Classify every request field (ordinary provider input / AI-consumed text / opaque model bytes /
Sim-durable storage) before implementing the tool and apply the shared projection or provenance
mechanism only where a concrete Sim `{{...}}` resolution path reaches a later model or log boundary.
Full rules and the required tests are in `.agents/skills/add-tools/SKILL.md` → "Resolved Secrets and
Provenance Boundaries".
## Step 3: Create Block
### File Location
`apps/sim/blocks/blocks/{service}.ts`
Follow `.agents/skills/add-block/SKILL.md` for the block structure, subBlock types,
`condition`/`dependsOn`/`required`/`mode` syntax, outputs, `canvasPresentation` sentences, and the
`{Service}BlockMeta` export (minimum 7 templates, plus `url` and `skills`). Every block declares
`canvasPresentation`; `bun run apps/sim/scripts/check-canvas-sentences.ts --block={service}` must
pass (CI runs `check:canvas-sentences --require-coverage`).
Three rules that are easy to get wrong when copying from existing blocks:
- Every remote `selectorKey` must use the unified server selector path. Apply the `add-selector` skill:
add browser-safe metadata to `apps/sim/lib/selectors/manifest.ts`, reuse or extract a server-only
provider listing primitive, and add a credential- and destination-bound server attachment. Do not
add a client provider fetcher, a provider-specific query key, browser token acquisition, or a
selector-only API route. The shared context builder sends only active `dependsOn` values and
preserves exact `{{KEY}}` environment references for server-side resolution.
- Basic/advanced pairs use a `canonicalParamId`; its constraints are in
`.claude/rules/sim-integrations.md` and the `add-block` skill → canonicalParamId Pattern.
- Every text-entry subBlock (`short-input`, `long-input`, `code`) and every selector declares a
`placeholder`; an empty box tells the user nothing. Secrets read `Enter your {thing}` (e.g.
`Enter your API key`), free text names what to type (`Enter branch name`), and formatted values
show the shape (`2023-01-01T00:00:00Z`, `1 to 1000`). An optional field with a server-side default
names that default (`Defaults to the database region`). Dropdowns, switches, and `oauth-input` do
not need one.
## Step 4: Add Icon
### File Location
`apps/sim/components/icons.tsx`
### Pattern
```typescript
export function {Service}Icon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{/* SVG paths from user-provided SVG */}
</svg>
)
}
```
### Getting Icons
**Do not search for icons yourself.** At the end of implementation, ask the user to paste the service's SVG (usually on its brand/press kit page).
Once the user provides the SVG:
1. Extract the SVG paths/content
2. Create a React component that spreads props
3. Ensure viewBox is preserved from the original SVG
### Theme-safety (bare rendering) — REQUIRED
The icon renders both inside its colored `bgColor` tile AND "bare" (no tile) on a
neutral page — e.g. the home **Suggested actions** list — in both light and dark
mode. A monochrome logo whose paths hardcode a single near-white or near-black
fill is invisible bare on the matching background (white-on-white in light mode,
black-on-black in dark mode).
Rules when adding the SVG:
- **Monochrome logos** (a single white or black mark): draw the shape with
`fill='currentColor'`, not `fill='#fff'` / `fill='#000000'`. It then inherits
white inside dark tiles, near-black inside light tiles (via
`getTileIconColorClass`), and the theme-aware `var(--text-icon)` bare — legible
everywhere. Do NOT set `iconColor` for these.
- **Multi-color brand logos** (their own vivid fills): keep the hardcoded fills.
They read on any background. Only set `iconColor` (a vivid brand hex, never a
near-black/near-white tile color) if the bare icon should adopt a brand tint.
- A large white shape with a tiny vivid accent (e.g. a logo where the body is the
white negative space) still vanishes bare — convert the body to `currentColor`.
Verify with `bun run check:bare-icons` (also runs in CI). It flags purely
monochrome hazards; for partial-accent logos, eyeball the suggested-actions list
in both light and dark mode.
## Step 5: Create Triggers (Optional)
If the service supports webhooks or needs polling, follow `.agents/skills/add-trigger/SKILL.md`
(directory layout, `buildTriggerSubBlocks`, provider handler, polling handler); then wire
`triggers.enabled` / `triggers.available` into the block and spread each trigger's
`getTrigger(id).subBlocks` after the tool subBlocks.
## Step 6: Register Everything
### Tools Registry (`apps/sim/tools/registry.ts`)
```typescript
// Add import (alphabetically)
import {
{service}Action1Tool,
{service}Action2Tool,
} from '@/tools/{service}'
// Add to tools object (alphabetically)
export const tools: Record<string, ExecutableToolConfig> = {
// ... existing tools ...
{service}_action1: {service}Action1Tool,
{service}_action2: {service}Action2Tool,
}
```
Then regenerate the generated tool metadata and commit it:
```bash
bun run tool-metadata:generate
```
Client code reads `params`/`outputs` from these artifacts rather than importing
the registry, so a tool you add, change or remove is invisible to the UI until they are regenerated,
and CI fails on stale ones. See `.agents/skills/tool-registry-boundary/SKILL.md`.
### Block Registry (`apps/sim/blocks/registry-maps.ts`)
The data maps (`BLOCK_REGISTRY` + `BLOCK_META_REGISTRY`) live in `registry-maps.ts`; `registry.ts` holds only the accessor functions. Add the import and an entry to each map alphabetically:
```typescript
// Add import (alphabetically)
import { {Service}Block, {Service}BlockMeta } from '@/blocks/blocks/{service}'
// Add to the config map (alphabetically)
export const BLOCK_REGISTRY: Record<string, BlockConfig> = {
// ... existing blocks ...
{service}: {Service}Block,
}
// Add to the catalog-meta map (alphabetically)
export const BLOCK_META_REGISTRY: Record<string, BlockMeta> = {
// ... existing metas ...
{service}: {Service}BlockMeta,
}
```
### Trigger Registry (`apps/sim/triggers/registry.ts`) - If triggers exist
```typescript
// Add import (alphabetically)
import {
{service}EventATrigger,
{service}EventBTrigger,
{service}WebhookTrigger,
} from '@/triggers/{service}'
// Add to TRIGGER_REGISTRY (alphabetically)
export const TRIGGER_REGISTRY: TriggerRegistry = {
// ... existing triggers ...
{service}_event_a: {service}EventATrigger,
{service}_event_b: {service}EventBTrigger,
{service}_webhook: {service}WebhookTrigger,
}
```
## Step 7: Configure Deployment Availability
Do this for every visible OAuth integration. API-key and unauthenticated integrations do not need
an OAuth client capability.
The block's `oauth-input.serviceId` is the canonical link between the generated integration catalog,
the OAuth service configuration, deployment availability, and the setup CLI.
1. Ensure the block has exactly one distinct OAuth `serviceId` and that it matches the canonical
service entry in `apps/sim/lib/oauth/oauth.ts`.
2. Confirm `resolveOAuthClientCapabilityId(serviceId)` resolves to the intended provider entry in
`OAUTH_CLIENT_CAPABILITIES` in `packages/deployment-config/src/env-capabilities.ts`. Google and
Microsoft service IDs deliberately share provider-level capabilities.
3. For a new OAuth provider, add the required client fields to `OAUTH_CLIENT_CAPABILITIES`, add
every referenced field to the env schema in `apps/sim/lib/core/config/env.ts`, and add the
matching `text` or `secret` entries to `OAUTH_CLIENT_SETUP_FIELDS` in
`packages/sim-setup/src/capability-config.ts`. Do not create integration-specific setup logic or
infer secret fields from naming; the CLI mapping is exhaustively checked against the runtime
fields.
4. If the canonical OAuth service has `serviceAccountProviderId`, run
`bun run deployment-config:generate` to refresh
`packages/deployment-config/src/service-account-providers.generated.ts`; never hand-edit the
generated provider-ID map. In `packages/deployment-config/src/service-account-metadata.ts`, use:
- no `deploymentRequirement` when the service-account path works independently of OAuth client fields;
- `'oauth-client'` when it requires the same deployment OAuth client fields;
- `'preview-gated'` when availability is controlled by the service-account preview block.
Never add a permissive fallback for missing capability metadata. A visible OAuth integration without
a resolvable capability must fail validation.
## Step 8: Generate and Validate the Catalog
Run `bun run tool-metadata:generate`, `bun run scripts/generate-docs.ts`,
`bun run deployment-config:generate`, then `bun run check:audits` (see the `validate-integration`
skill → Regenerate Derived Artifacts for the full list and what each check verifies).
The docs generator creates `apps/docs/content/docs/integrations/{service}.mdx` — one page per service carrying the block's Actions and, if it has one, its Triggers section. Never hand-edit generated pages; the only editable region is the `{/* MANUAL-CONTENT */}` block (see `scripts/README.md`).
Every generated integration page carries a hand-written intro directly under `<BlockInfoCard />`. The
generator preserves it across regenerations, so write it once after the first generate:
```mdx
{/* MANUAL-CONTENT-START:intro */}
[{Service}](https://service.com/) is {one sentence on what the service is}.
With the {Service} block, you can:
- **{Capability}**: {what the operations in this group do}
- **{Capability}**: {...}
{How to connect: which credential to create and where, if it is not OAuth.}
In Sim, the {Service} block lets your agents {concrete workflow uses}.
{/* MANUAL-CONTENT-END */}
```
Group the bullets by what the user gets done, not one bullet per tool. Only describe operations the
block actually ships. Follow `.claude/rules/constitution.md` for voice. Re-run
`bun run scripts/generate-docs.ts` afterwards and confirm the section survived unchanged.
The docs generator refreshes `packages/deployment-config/src/integrations.json`, and the deployment
config generator projects service-account provider IDs from that catalog plus the canonical OAuth
registry. The checks compare both committed projections with their sources. Review the generated
diff and keep only intentional changes.
## V2 Integration Pattern
If creating V2 versions (API-aligned outputs):
1. **V2 Tools** - Add `_v2` suffix, version `2.0.0`, flat outputs
2. **V2 Block** - Add `_v2` type, use `createVersionedToolSelector`
3. **V1 Block** - Add `(Legacy)` to name, set `hideFromToolbar: true`, and add
`sunset: { status: 'legacy', replacedBy: '{service}_v2' }` — `check-block-registry`
fails a legacy block with no `replacedBy`, and the amber legacy badge plus its
click-to-upgrade action read from that field.
**Only add `replacedBy` once the target is GA.** The same check also fails when
the target is unregistered, itself sunset, or still `preview: true`. If v2 is
preview-gated, leave v1 alone until GA and drop `preview` in the *same commit*
that adds the sunset — splitting them breaks the build in between.
4. **Registry** - Register both versions
```typescript
// In registry
{service}: {Service}Block, // V1 (legacy, hidden)
{service}_v2: {Service}V2Block, // V2 (visible)
```
## Complete Checklist
### Tools
- [ ] Created `tools/{service}/` directory
- [ ] Created `types.ts` with all interfaces
- [ ] Created tool file for each operation
- [ ] Chose exactly one boundary per tool: registered `InternalToolConfig.operation` or absolute
external HTTP(S) `ToolConfig.request`
- [ ] No tool points to `/api/...`, constructs a URL back to Sim, declares `request.internal` or a
View on GitHub