Validate an existing Sim integration (tools, block, registry, and resolved-secret/model-input boundaries) against the service's API docs and Sim execution conventions
61
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/validate-integration/SKILL.mdYou are an expert auditor for Sim integrations. Your job is to thoroughly validate that an existing integration is correct, complete, and follows all conventions.
When the user asks you to validate an integration:
Read every file for the integration — do not skip any:
apps/sim/tools/{service}/ # All tool files, types.ts, index.ts
apps/sim/blocks/blocks/{service}.ts # Block definition
apps/sim/tools/registry.ts # Tool registry entries for this service
apps/sim/blocks/registry-maps.ts # Block + meta registry entry (BLOCK_REGISTRY / BLOCK_META_REGISTRY)
apps/sim/components/icons.tsx # Icon definition
apps/sim/lib/auth/auth.ts # OAuth config — should use getCanonicalScopesForProvider()
apps/sim/lib/oauth/oauth.ts # OAuth provider config — single source of truth for scopes
apps/sim/lib/oauth/utils.ts # Scope utilities, SCOPE_DESCRIPTIONS for modal UI
apps/sim/lib/core/config/env-capabilities.ts # OAuth client runtime capability source of truth
apps/sim/lib/core/config/env.ts # Runtime env schema for capability fields
scripts/setup/capability-config.ts # Exhaustive CLI input-mode mapping for OAuth fields
apps/sim/lib/integrations/integrations.json # Generated client-safe integration catalog
apps/sim/lib/integrations/service-account-metadata.ts # Lightweight service-account projectionFetch the official API docs for the service. This is the source of truth for:
If the official docs do not clearly show the response JSON shape for an endpoint, you MUST tell the user instead of guessing.
If a response schema is unknown, the validation must explicitly call that out and require:
For every tool file, check:
snake_case: {service}_{action} (e.g., x_create_tweet, slack_send_message)name is human-readable (e.g., 'X Create Tweet')description is a concise one-liner describing what it doesversion is set ('1.0.0' or '2.0.0' for V2)required: truerequired: falserequired: true or required: false — never omitted'string', 'number', 'boolean', 'json')'hidden' — ONLY for OAuth access tokens and system-injected params'user-only' — for API keys, credentials, and account-specific IDs the user must provide'user-or-llm' — for everything else (search queries, content, filters, IDs that could come from other blocks)description that explains what it doesAuthorization: Bearer ${params.accessToken}Content-Type header is set for POST/PUT/PATCH requests.trim()-ed to prevent copy-paste whitespace errors`https://api.service.com/v1/${params.id.trim()}`await response.json())data.data vs data vs data.results)?? null?? []optional: true is set on fields that may not exist in all responsestype: 'json' and the shape is known, properties defines the inner fields (tool outputs only — block outputs do not support properties)type: 'array', items defines the item structure with properties (tool outputs only)XCreateTweetParams)ToolResponse)? in the interface (e.g., replyTo?: string)XTweetResponse shared across tweet tools)export * from './types')For every request field, determine whether it is ordinary API input, model-visible text/structured content, opaque model input, or a value persisted into Sim-owned durable storage.
Treat model-input provenance as opt-in. Require official documentation or an unambiguous local execution path proving that the exact field reaches an AI model. If the evidence is ambiguous, leave the integration unchanged; do not infer a model boundary merely from natural-language, search, extraction, or "AI-powered" marketing terminology.
request.modelInput with mode: 'project' and a
minimal exact selector; nested/JSON-string adapters preserve shape through applyProjectedrequest.modelInput, projected before the existing formatter parses it, and has deterministic
formatter behavior when a whole-value placeholder is invalid for the serialized grammarprivateProvenance (or mode: 'private-provenance'), and the route validates
validateOpaqueModelInputProvenance before model egress; storage keys, paths, signed URLs,
and ordinary remote URLs are not treated as byte provenance, while tracked stored bytes are
authorized independently at the owning model-egress boundaryfile_writerequest.secretProvenance; authenticated receivers validate the exact selection
and scope, strip private metadata, and persist, import, or propagate it at the owning boundarydirectExecution; proven
model-visible external fields use projection, while other external inputs remain unchangedtransformResponse or tool-local helper blanket-sanitizes ordinary third-party results;
only execution-scoped, activated Sim provenance is projected at shared model/log boundaries{{...}} resolution path and a later
persistence/model/log crossing; there is no generic handling for arbitrary filenames,
metadata, provider results, or API payloads{{NAME}} projection, unproven identical public text, nested and serialized
shape handling, unchanged ordinary external inputs, malformed/incomplete metadata, headerless
legacy requests, metadata stripping, and durable legacy/stale/scope cases when applicableTreat a missing or bypassed model, durable, or internal-execution provenance boundary as critical. Do not fix it with a tool-specific string replacer or by sanitizing every provider result; repair the shared request, authenticated internal-route, persistence, or re-entry boundary that owns the data.
This is the most important validation — the block must be perfectly aligned with every tool it references.
For each tool in tools.access:
tools.config.tool function correctly maps to it)accessToken) has a corresponding subBlock input that is:
condition)required: true (or conditionally required)id values are unique across the entire block — no duplicates even across different conditionstools.config.tool function returns the correct tool ID for every possible operation valuetools.config.params function correctly maps subBlock IDs to tool param names when they differtools.access{ field: 'operation', value: 'x_create_tweet' }{ field: 'operation', value: ['x_create_tweet', 'x_delete_tweet'] }{ field: 'operation', value: 'delete', not: true }{ field: 'op', value: 'send', and: { field: 'type', value: 'dm' } }dependsOn is set for fields that need other values (selectors depending on credential, cascading dropdowns)dropdownshort-inputlong-inputdropdown with Yes/No options (not switch unless purely UI toggle)oauth-input with correct serviceIdvalue: () => 'default' is set for dropdowns with a sensible defaultmode: 'advanced':
mode: 'advanced'mode: 'advanced'wandConfig with generationType: 'timestamp'wandConfig with a descriptive promptwandConfig with format examples in the promptwandConfig prompts end with "Return ONLY the [format] - no explanations, no extra text."wandConfig.placeholder describes what to type in natural languagetools.access lists every tool ID the block can use — none missingtools.config.tool returns the correct tool ID for each operationtools.config.params (runs at execution time), NOT in tools.config.tool (runs at serialization time before variable resolution)tools.config.params handles:
Number() conversion for numeric params that come as strings from inputsBoolean / string-to-boolean conversion for toggle paramsundefined conversion for optional dropdown valuesNumber(), JSON.parse(), or other coercions in tools.config.tool — these would destroy dynamic references like <Block.output>'string', 'number', 'boolean', 'json')type: 'json' outputs describe inner fields in the description string: 'User profile (id, name, username, bio)' or '[{address, status, type}]' for arraysproperties: {...} field on block outputs. Block-level OutputFieldDefinition (from @sim/workflow-types/blocks) only accepts { type, description?, condition?, hiddenFromDisplay? }. Nested properties is a tool-level construct (OutputProperty) — adding it to a block output will fail TypeScript at build timetype: 'json' with vague descriptions like 'Response data'condition if supported, or document which operations return themtype is snake_case (e.g., 'x', 'cloudflare')name is human-readable (e.g., 'X', 'Cloudflare')description is a concise one-linerlongDescription provides detail for docsdocsLink points to 'https://docs.sim.ai/integrations/{service}'category is 'tools'bgColor uses the service's brand color hexicon references the correct icon component from @/components/iconsauthMode is set correctly (AuthMode.OAuth or AuthMode.ApiKey)blocks/registry-maps.ts (BLOCK_REGISTRY / BLOCK_META_REGISTRY) alphabetically{Service}BlockMeta is exported in the same file as the blockicon, title, prompt, modules, category, and tagsalsoIntegrations is set on any template whose prompt references another serviceskills present (3–5 mainstream, 2–3 niche), each grounded in tools.access — flag any skill implying an unsupported actionname (≤64 chars, unique), a one-line description, and markdown content with # Title + ## Steps + an output/guidance sectioninputs section lists all subBlock params that the block acceptscanonicalParamId, inputs list the canonical ID (not the raw subBlock IDs)Scopes are centralized — the single source of truth is OAUTH_PROVIDERS in lib/oauth/oauth.ts.
lib/oauth/oauth.ts under OAUTH_PROVIDERS[provider].services[service].scopesauth.ts uses getCanonicalScopesForProvider(providerId) — NOT a hardcoded arrayrequiredScopes uses getScopesForService(serviceId) — NOT a hardcoded arrayauth.ts or block files (should all use utility functions)SCOPE_DESCRIPTIONS within lib/oauth/utils.tsThe deployment UI and setup CLI do not infer OAuth client fields from scopes. They resolve the
block's generated oauthServiceId through the application-owned capability catalog.
oauth-input.serviceIdresolveOAuthClientCapabilityId(serviceId) returns the intended provider capabilityOAUTH_CLIENT_CAPABILITIESapps/sim/lib/core/config/env.tstext or secret entry in OAUTH_CLIENT_SETUP_FIELDS; no CLI naming heuristic is requiredbun run setup integration <capabilityId> is the command emitted by availability; the CLI has only the exhaustive input-mode projection, not a second runtime provider definitionserviceAccountProviderId,
SERVICE_ACCOUNT_METADATA_BY_OAUTH_SERVICE_ID[serviceId] has the same provider IDdeploymentRequirement matches how that credential actually works:
omitted for an independent path, 'oauth-client' when it needs the OAuth client fields, or
'preview-gated' when controlled by a preview blockTreat a missing capability as critical: runtime availability intentionally throws instead of silently exposing an unusable integration.
If any tools support pagination:
pagination_token vs next_token vs cursor)nextToken, cursor, etc.) are included in tool outputsmode: 'advanced'If any tool lists, searches, exports, imports, downloads, uploads, paginates, batches, transforms arrays, or reads file/HTTP bodies, read .agents/skills/memory-load-check/SKILL.md and apply it to the integration.
Promise.all fan-outstransformResponse checks for error conditions before accessing dataresponse.ok or status codes)Group findings by severity:
Critical (will cause runtime errors or incorrect behavior):
required flagtools.accessauth.ts that tools needserviceId missing from the deployment capability catalogtools.config.tool returning wrong tool ID for an operationtools.config.tool instead of tools.config.paramsNULL dataWarning (follows conventions incorrectly or has usability issues):
mode: 'advanced'wandConfig on timestamp/complex fieldsvisibility on params (e.g., 'hidden' instead of 'user-or-llm')optional: true on nullable outputstype: 'json' without property descriptions.trim() on ID fields in request URLs?? null on nullable response fieldsgetScopesForService() / getCanonicalScopesForProvider()SCOPE_DESCRIPTIONS within lib/oauth/utils.tsSuggestion (minor improvements):
longDescription or docsLinkwandConfigAfter reporting, fix every critical and warning issue. Apply suggestions where they don't add unnecessary complexity.
Several files are generated from tool and block definitions. Editing a tool or block WITHOUT regenerating them fails CI, so run these before pushing:
bun run tool-metadata:generate # repo root — apps/sim/tools/generated/*
bun run scripts/generate-docs.ts # docs .mdx + lib/integrations/integrations.json + docs icons
bun run integration-catalog:check # registry ↔ committed deployment metadata drifttool-metadata:generate — required whenever a tool's outputs, params, or descriptions change. CI enforces this with bun run tool-metadata:check, which fails with "Generated tool metadata is stale". This is the easiest gate to miss, because nothing in the tool file hints that a generated artifact mirrors it.generate-docs — required whenever block metadata changes (bgColor, name, description, operations, outputs). Regenerates the integration .mdx, integrations.json, and the docs copy of components/icons.tsx.integration-catalog:check — loads the executable block registry, derives visible integration
deployment fields, and compares them with the committed catalog. It catches missing/unexpected
entries and stale auth/service IDs without loading the executable registry in client code.Always diff the regen output before committing. These generators rewrite every file they own, so they will also sweep in unrelated drift that accumulated on the base branch — pages losing sections, unrelated icons appearing. Keep only the hunks belonging to the integration under validation and git checkout -- the rest, otherwise an unrelated doc regression rides along in the PR. Verify no page was silently dropped by comparing the directory listing before and after.
If an icon changed, apps/sim/components/icons.tsx is the source of truth and apps/docs/components/icons.tsx is its generated mirror — they must end up byte-identical for that component.
After fixing, confirm:
bun run lint passes with no fixes neededbun run integration-catalog:check passesbun test apps/sim/lib/integrations/availability.server.test.ts passesSCOPE_DESCRIPTIONS within lib/oauth/utils.ts for all scopesserviceId resolves to the intended OAUTH_CLIENT_CAPABILITIES entry and all capability fields exist in the env schemaintegrations.json when block metadata changed and ran bun run integration-catalog:check.agents/skills/memory-load-check/SKILL.md when tools list/search/download/import/export/batch data{Service}BlockMeta exported with at least 7 templatesbun run tool-metadata:generate if any tool outputs/params changed, and confirmed bun run tool-metadata:check passesbun run generate-docs if any block metadata changed, and reverted unrelated drift the generator swept inbun run lint after fixes417ae20
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.