From b298b4aeaf312d0a7b77258344a11998fe5f04fd Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 15:23:22 -0700 Subject: [PATCH 1/8] feat(ironclad): add Ironclad integration with OAuth --- apps/docs/components/icons.tsx | 11 + apps/docs/components/ui/icon-mapping.ts | 2 + apps/docs/content/docs/en/tools/ironclad.mdx | 259 +++++++++++++++ apps/docs/content/docs/en/tools/meta.json | 1 + .../integrations/data/icon-mapping.ts | 2 + .../integrations/data/integrations.json | 67 ++++ apps/sim/blocks/blocks/ironclad.ts | 304 ++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 11 + apps/sim/lib/auth/auth.ts | 50 +++ apps/sim/lib/core/config/env.ts | 2 + apps/sim/lib/oauth/oauth.ts | 39 +++ apps/sim/lib/oauth/utils.ts | 12 + apps/sim/tools/ironclad/add_comment.ts | 71 ++++ apps/sim/tools/ironclad/cancel_workflow.ts | 68 ++++ apps/sim/tools/ironclad/create_record.ts | 100 ++++++ apps/sim/tools/ironclad/create_workflow.ts | 85 +++++ apps/sim/tools/ironclad/get_record.ts | 67 ++++ apps/sim/tools/ironclad/get_workflow.ts | 68 ++++ apps/sim/tools/ironclad/index.ts | 27 ++ apps/sim/tools/ironclad/list_records.ts | 90 ++++++ .../tools/ironclad/list_workflow_approvals.ts | 65 ++++ .../tools/ironclad/list_workflow_comments.ts | 64 ++++ apps/sim/tools/ironclad/list_workflows.ts | 87 +++++ apps/sim/tools/ironclad/types.ts | 199 ++++++++++++ apps/sim/tools/ironclad/update_record.ts | 75 +++++ .../ironclad/update_workflow_metadata.ts | 75 +++++ apps/sim/tools/registry.ts | 26 ++ 28 files changed, 1929 insertions(+) create mode 100644 apps/docs/content/docs/en/tools/ironclad.mdx create mode 100644 apps/sim/blocks/blocks/ironclad.ts create mode 100644 apps/sim/tools/ironclad/add_comment.ts create mode 100644 apps/sim/tools/ironclad/cancel_workflow.ts create mode 100644 apps/sim/tools/ironclad/create_record.ts create mode 100644 apps/sim/tools/ironclad/create_workflow.ts create mode 100644 apps/sim/tools/ironclad/get_record.ts create mode 100644 apps/sim/tools/ironclad/get_workflow.ts create mode 100644 apps/sim/tools/ironclad/index.ts create mode 100644 apps/sim/tools/ironclad/list_records.ts create mode 100644 apps/sim/tools/ironclad/list_workflow_approvals.ts create mode 100644 apps/sim/tools/ironclad/list_workflow_comments.ts create mode 100644 apps/sim/tools/ironclad/list_workflows.ts create mode 100644 apps/sim/tools/ironclad/types.ts create mode 100644 apps/sim/tools/ironclad/update_record.ts create mode 100644 apps/sim/tools/ironclad/update_workflow_metadata.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 0cf5773ca48..086e42c494f 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4186,6 +4186,17 @@ export function IntercomIcon(props: SVGProps) { ) } +export function IroncladIcon(props: SVGProps) { + return ( + + + + ) +} + export function LoopsIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 900229eaad7..88bd21aea8f 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -84,6 +84,7 @@ import { IncidentioIcon, InfisicalIcon, IntercomIcon, + IroncladIcon, JinaAIIcon, JiraIcon, JiraServiceManagementIcon, @@ -258,6 +259,7 @@ export const blockTypeToIconMap: Record = { incidentio: IncidentioIcon, infisical: InfisicalIcon, intercom_v2: IntercomIcon, + ironclad: IroncladIcon, jina: JinaAIIcon, jira: JiraIcon, jira_service_management: JiraServiceManagementIcon, diff --git a/apps/docs/content/docs/en/tools/ironclad.mdx b/apps/docs/content/docs/en/tools/ironclad.mdx new file mode 100644 index 00000000000..e5469c2942b --- /dev/null +++ b/apps/docs/content/docs/en/tools/ironclad.mdx @@ -0,0 +1,259 @@ +--- +title: Ironclad +description: Contract lifecycle management with Ironclad +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Ironclad](https://ironcladapp.com/) is a leading contract lifecycle management (CLM) platform that helps legal teams streamline contract creation, negotiation, approval, and storage. It enables organizations to manage the entire contract process from start to finish in a single, unified platform. + +With Ironclad, you can: + +- **Automate contract workflows**: Create and manage contract workflows using configurable templates with built-in approval routing +- **Manage records**: Store and organize contract records with custom metadata, properties, and linked relationships +- **Track approvals**: Monitor approval status across workflows with conditional approval groups +- **Collaborate with comments**: Add and view comments on workflows for team communication and audit trails + +In Sim, the Ironclad integration enables your agents to interact with Ironclad's workflow and records APIs programmatically. This allows for seamless contract operations like creating new workflows from templates, updating contract metadata, managing records, and tracking approvals - all within your agent workflows. Use Ironclad to automate contract lifecycle processes, keep records in sync, and enable your agents to participate in contract management decisions. +{/* MANUAL-CONTENT-END */} + +## Usage Instructions + +Manage workflows and records in Ironclad. Create and track contract workflows, manage records, view approvals, add comments, and update metadata. Requires an Ironclad OAuth connection. + + + +## Tools + +### `ironclad_create_workflow` + +Create a new workflow in Ironclad using a specified template and attributes. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `template` | string | Yes | The template ID to use for the workflow | +| `attributes` | string | No | JSON object of workflow attributes | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The ID of the created workflow | +| `status` | string | The status of the workflow | +| `template` | string | The template used for the workflow | +| `creator` | string | The creator of the workflow | + +### `ironclad_list_workflows` + +List all workflows in Ironclad with pagination support. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `page` | number | No | Page number \(starting from 0\) | +| `perPage` | number | No | Number of results per page \(max 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `workflows` | json | List of workflows | +| `page` | number | Current page number | +| `pageSize` | number | Number of results per page | +| `count` | number | Total number of workflows | + +### `ironclad_get_workflow` + +Retrieve details of a specific workflow by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The workflow ID | +| `status` | string | The workflow status | +| `template` | string | The template used for the workflow | +| `creator` | string | The creator of the workflow | +| `step` | string | The current step of the workflow | +| `attributes` | json | The workflow attributes | + +### `ironclad_update_workflow_metadata` + +Update attributes on a workflow. The workflow must be in the Review step. Supports set and remove actions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | +| `actions` | string | Yes | JSON array of actions. Each action has "action" \(set/remove\), "field", and optionally "value". | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the update was successful | + +### `ironclad_cancel_workflow` + +Cancel a workflow instance by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow to cancel | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the cancellation was successful | + +### `ironclad_list_workflow_approvals` + +List all triggered approvals for a specific workflow. Conditional approvals that have not been triggered will not appear. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `approvals` | json | List of triggered approval groups for the workflow | + +### `ironclad_add_comment` + +Add a comment to a workflow. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | +| `comment` | string | Yes | The comment text to add | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the comment was added successfully | + +### `ironclad_list_workflow_comments` + +List all comments on a workflow. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workflowId` | string | Yes | The unique identifier of the workflow | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `comments` | json | List of comments on the workflow | + +### `ironclad_create_record` + +Create a new record in Ironclad with a specified type, name, and properties. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recordType` | string | Yes | The record type \(e.g., "contract", "Statement of Work"\) | +| `name` | string | Yes | A human-readable name for the record | +| `properties` | string | No | JSON object of properties. Each property has a "type" \(string/number/email/date/monetary_amount\) and "value". | +| `links` | string | No | JSON array of linked record objects, each with a "recordId" field | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The ID of the created record | +| `name` | string | The name of the record | +| `type` | string | The type of the record | + +### `ironclad_list_records` + +List all records in Ironclad with pagination and optional filtering by last updated time. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `page` | number | No | Page number \(starting from 0\) | +| `pageSize` | number | No | Number of results per page \(max 100\) | +| `lastUpdated` | string | No | Filter records updated on or after this ISO 8601 timestamp | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `records` | json | List of records | +| `page` | number | Current page number | +| `pageSize` | number | Number of results per page | +| `count` | number | Total number of records | + +### `ironclad_get_record` + +Retrieve details of a specific record by its ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recordId` | string | Yes | The unique identifier of the record | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The record ID | +| `name` | string | The record name | +| `type` | string | The record type | +| `properties` | json | The record properties | +| `createdAt` | string | When the record was created | +| `updatedAt` | string | When the record was last updated | + +### `ironclad_update_record` + +Update metadata fields on an existing record. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recordId` | string | Yes | The unique identifier of the record to update | +| `properties` | string | Yes | JSON object of fields to update \(e.g., \{"fieldName": "newValue"\}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | The record ID | +| `name` | string | The record name | +| `type` | string | The record type | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 9ff8f3ff78a..4a25e3514a7 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -79,6 +79,7 @@ "incidentio", "infisical", "intercom", + "ironclad", "jina", "jira", "jira_service_management", diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 7580b713c57..8036edba919 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -84,6 +84,7 @@ import { IncidentioIcon, InfisicalIcon, IntercomIcon, + IroncladIcon, JinaAIIcon, JiraIcon, JiraServiceManagementIcon, @@ -258,6 +259,7 @@ export const blockTypeToIconMap: Record = { incidentio: IncidentioIcon, infisical: InfisicalIcon, intercom_v2: IntercomIcon, + ironclad: IroncladIcon, jina: JinaAIIcon, jira: JiraIcon, jira_service_management: JiraServiceManagementIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 7d8cf706be5..9adc405d1e6 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -5758,6 +5758,73 @@ "integrationType": "customer-support", "tags": ["customer-support", "messaging"] }, + { + "type": "ironclad", + "slug": "ironclad", + "name": "Ironclad", + "description": "Contract lifecycle management with Ironclad", + "longDescription": "Manage workflows and records in Ironclad. Create and track contract workflows, manage records, view approvals, add comments, and update metadata. Requires an Ironclad OAuth connection.", + "bgColor": "#FFFFFF", + "iconName": "IroncladIcon", + "docsUrl": "https://docs.sim.ai/tools/ironclad", + "operations": [ + { + "name": "Create Workflow", + "description": "Create a new workflow in Ironclad using a specified template and attributes." + }, + { + "name": "List Workflows", + "description": "List all workflows in Ironclad with pagination support." + }, + { + "name": "Get Workflow", + "description": "Retrieve details of a specific workflow by its ID." + }, + { + "name": "Update Workflow Metadata", + "description": "Update attributes on a workflow. The workflow must be in the Review step. Supports set and remove actions." + }, + { + "name": "Cancel Workflow", + "description": "Cancel a workflow instance by its ID." + }, + { + "name": "List Workflow Approvals", + "description": "List all triggered approvals for a specific workflow. Conditional approvals that have not been triggered will not appear." + }, + { + "name": "Add Comment", + "description": "Add a comment to a workflow." + }, + { + "name": "List Workflow Comments", + "description": "List all comments on a workflow." + }, + { + "name": "Create Record", + "description": "Create a new record in Ironclad with a specified type, name, and properties." + }, + { + "name": "List Records", + "description": "List all records in Ironclad with pagination and optional filtering by last updated time." + }, + { + "name": "Get Record", + "description": "Retrieve details of a specific record by its ID." + }, + { + "name": "Update Record", + "description": "Update metadata fields on an existing record." + } + ], + "operationCount": 12, + "triggers": [], + "triggerCount": 0, + "authType": "oauth", + "category": "tools", + "integrationType": "documents", + "tags": ["e-signatures", "document-processing"] + }, { "type": "jina", "slug": "jina", diff --git a/apps/sim/blocks/blocks/ironclad.ts b/apps/sim/blocks/blocks/ironclad.ts new file mode 100644 index 00000000000..5d173f73d23 --- /dev/null +++ b/apps/sim/blocks/blocks/ironclad.ts @@ -0,0 +1,304 @@ +import { IroncladIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +export const IroncladBlock: BlockConfig = { + type: 'ironclad', + name: 'Ironclad', + description: 'Contract lifecycle management with Ironclad', + longDescription: + 'Manage workflows and records in Ironclad. Create and track contract workflows, manage records, view approvals, add comments, and update metadata. Requires an Ironclad OAuth connection.', + docsLink: 'https://docs.sim.ai/tools/ironclad', + category: 'tools', + integrationType: IntegrationType.Documents, + tags: ['e-signatures', 'document-processing'], + bgColor: '#FFFFFF', + icon: IroncladIcon, + authMode: AuthMode.OAuth, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Workflow', id: 'create_workflow' }, + { label: 'List Workflows', id: 'list_workflows' }, + { label: 'Get Workflow', id: 'get_workflow' }, + { label: 'Update Workflow Metadata', id: 'update_workflow_metadata' }, + { label: 'Cancel Workflow', id: 'cancel_workflow' }, + { label: 'List Workflow Approvals', id: 'list_workflow_approvals' }, + { label: 'Add Comment', id: 'add_comment' }, + { label: 'List Workflow Comments', id: 'list_workflow_comments' }, + { label: 'Create Record', id: 'create_record' }, + { label: 'List Records', id: 'list_records' }, + { label: 'Get Record', id: 'get_record' }, + { label: 'Update Record', id: 'update_record' }, + ], + value: () => 'list_workflows', + }, + { + id: 'credential', + title: 'Ironclad Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + serviceId: 'ironclad', + requiredScopes: getScopesForService('ironclad'), + placeholder: 'Select Ironclad account', + required: true, + }, + { + id: 'manualCredential', + title: 'Ironclad Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + + // Create Workflow fields + { + id: 'template', + title: 'Template ID', + type: 'short-input', + placeholder: 'Enter workflow template ID', + condition: { field: 'operation', value: 'create_workflow' }, + required: { field: 'operation', value: 'create_workflow' }, + }, + { + id: 'attributes', + title: 'Attributes', + type: 'long-input', + placeholder: '{"counterpartyName": "Acme Corp"}', + condition: { field: 'operation', value: 'create_workflow' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object of Ironclad workflow attributes based on the description. Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the workflow attributes...', + generationType: 'json-object', + }, + }, + + // Workflow ID field (shared by get, update metadata, cancel, approvals, comments) + { + id: 'workflowId', + title: 'Workflow ID', + type: 'short-input', + placeholder: 'Enter workflow ID', + condition: { + field: 'operation', + value: [ + 'get_workflow', + 'update_workflow_metadata', + 'cancel_workflow', + 'list_workflow_approvals', + 'add_comment', + 'list_workflow_comments', + ], + }, + required: { + field: 'operation', + value: [ + 'get_workflow', + 'update_workflow_metadata', + 'cancel_workflow', + 'list_workflow_approvals', + 'add_comment', + 'list_workflow_comments', + ], + }, + }, + + // Update Workflow Metadata fields + { + id: 'actions', + title: 'Actions', + type: 'long-input', + placeholder: '[{"action": "set", "field": "status", "value": "Approved"}]', + condition: { field: 'operation', value: 'update_workflow_metadata' }, + required: { field: 'operation', value: 'update_workflow_metadata' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Ironclad workflow update actions. Each action has "action" ("set" or "remove"), "field" (attribute name), and optionally "value". Return ONLY the JSON array - no explanations, no extra text.', + placeholder: 'Describe the updates (e.g., "set status to Approved")...', + generationType: 'json-object', + }, + }, + + // Add Comment field + { + id: 'comment', + title: 'Comment', + type: 'long-input', + placeholder: 'Enter comment text', + condition: { field: 'operation', value: 'add_comment' }, + required: { field: 'operation', value: 'add_comment' }, + }, + + // Create Record fields + { + id: 'recordType', + title: 'Record Type', + type: 'short-input', + placeholder: 'e.g., contract, Statement of Work', + condition: { field: 'operation', value: 'create_record' }, + required: { field: 'operation', value: 'create_record' }, + }, + { + id: 'name', + title: 'Record Name', + type: 'short-input', + placeholder: 'Enter record name', + condition: { field: 'operation', value: 'create_record' }, + required: { field: 'operation', value: 'create_record' }, + }, + { + id: 'properties', + title: 'Properties', + type: 'long-input', + placeholder: '{"counterpartyName": {"type": "string", "value": "Acme Corp"}}', + condition: { field: 'operation', value: ['create_record', 'update_record'] }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object of Ironclad record properties. Each property has a "type" (string/number/email/date/monetary_amount) and "value". Return ONLY the JSON object - no explanations, no extra text.', + placeholder: 'Describe the record properties...', + generationType: 'json-object', + }, + }, + { + id: 'links', + title: 'Linked Records', + type: 'long-input', + placeholder: '[{"recordId": "abc-123"}]', + condition: { field: 'operation', value: 'create_record' }, + mode: 'advanced', + }, + + // Get/Update Record fields + { + id: 'recordId', + title: 'Record ID', + type: 'short-input', + placeholder: 'Enter record ID', + condition: { field: 'operation', value: ['get_record', 'update_record'] }, + required: { field: 'operation', value: ['get_record', 'update_record'] }, + }, + + // List pagination fields + { + id: 'page', + title: 'Page', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: ['list_workflows', 'list_records'] }, + mode: 'advanced', + }, + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: ['list_workflows', 'list_records'] }, + mode: 'advanced', + }, + { + id: 'lastUpdated', + title: 'Last Updated After', + type: 'short-input', + placeholder: '2024-01-01T00:00:00Z', + condition: { field: 'operation', value: 'list_records' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 timestamp based on the description. Return ONLY the timestamp string - no explanations, no extra text.', + placeholder: 'Describe the date (e.g., "yesterday", "last week")...', + generationType: 'timestamp', + }, + }, + ], + tools: { + access: [ + 'ironclad_create_workflow', + 'ironclad_list_workflows', + 'ironclad_get_workflow', + 'ironclad_update_workflow_metadata', + 'ironclad_cancel_workflow', + 'ironclad_list_workflow_approvals', + 'ironclad_add_comment', + 'ironclad_list_workflow_comments', + 'ironclad_create_record', + 'ironclad_list_records', + 'ironclad_get_record', + 'ironclad_update_record', + ], + config: { + tool: (params) => `ironclad_${params.operation}`, + params: (params) => { + const { oauthCredential, ...rest } = params + const result: Record = { + credential: oauthCredential, + ...rest, + } + if (params.page) result.page = Number(params.page) + if (params.pageSize) { + if (params.operation === 'list_workflows') { + result.perPage = Number(params.pageSize) + result.pageSize = undefined + } else { + result.pageSize = Number(params.pageSize) + } + } + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Ironclad OAuth credential' }, + template: { type: 'string', description: 'Workflow template ID' }, + attributes: { type: 'string', description: 'Workflow attributes (JSON)' }, + workflowId: { type: 'string', description: 'Workflow ID' }, + actions: { type: 'string', description: 'Update actions (JSON array)' }, + comment: { type: 'string', description: 'Comment text' }, + recordType: { type: 'string', description: 'Record type' }, + name: { type: 'string', description: 'Record name' }, + properties: { type: 'string', description: 'Record properties (JSON)' }, + links: { type: 'string', description: 'Linked records (JSON array)' }, + recordId: { type: 'string', description: 'Record ID' }, + page: { type: 'number', description: 'Page number' }, + pageSize: { type: 'number', description: 'Results per page' }, + lastUpdated: { type: 'string', description: 'Filter by last updated timestamp' }, + }, + outputs: { + // Workflow outputs + id: { type: 'string', description: 'Resource ID' }, + status: { type: 'string', description: 'Workflow status' }, + template: { type: 'string', description: 'Workflow template' }, + creator: { type: 'string', description: 'Workflow creator' }, + step: { type: 'string', description: 'Current workflow step' }, + attributes: { type: 'json', description: 'Workflow attributes' }, + approvals: { type: 'json', description: 'Workflow approval groups' }, + comments: { type: 'json', description: 'Workflow comments' }, + // Record outputs + name: { type: 'string', description: 'Record name' }, + type: { type: 'string', description: 'Record type' }, + properties: { type: 'json', description: 'Record properties' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + // List outputs + workflows: { type: 'json', description: 'List of workflows' }, + records: { type: 'json', description: 'List of records' }, + page: { type: 'number', description: 'Current page number' }, + pageSize: { type: 'number', description: 'Results per page' }, + count: { type: 'number', description: 'Total count' }, + // Action outputs + success: { type: 'boolean', description: 'Whether the operation succeeded' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 15363a8ad53..bc8c70d903d 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -87,6 +87,7 @@ import { IncidentioBlock } from '@/blocks/blocks/incidentio' import { InfisicalBlock } from '@/blocks/blocks/infisical' import { InputTriggerBlock } from '@/blocks/blocks/input_trigger' import { IntercomBlock, IntercomV2Block } from '@/blocks/blocks/intercom' +import { IroncladBlock } from '@/blocks/blocks/ironclad' import { JinaBlock } from '@/blocks/blocks/jina' import { JiraBlock } from '@/blocks/blocks/jira' import { JiraServiceManagementBlock } from '@/blocks/blocks/jira_service_management' @@ -307,6 +308,7 @@ export const registry: Record = { input_trigger: InputTriggerBlock, intercom: IntercomBlock, intercom_v2: IntercomV2Block, + ironclad: IroncladBlock, jina: JinaBlock, jira: JiraBlock, jira_service_management: JiraServiceManagementBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 0cf5773ca48..086e42c494f 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4186,6 +4186,17 @@ export function IntercomIcon(props: SVGProps) { ) } +export function IroncladIcon(props: SVGProps) { + return ( + + + + ) +} + export function LoopsIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 7bfaa64889d..16253c55396 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -539,6 +539,7 @@ export const auth = betterAuth({ 'sharepoint', 'jira', 'airtable', + 'ironclad', 'box', 'dropbox', 'salesforce', @@ -1883,6 +1884,55 @@ export const auth = betterAuth({ }, }, + // Ironclad provider + { + providerId: 'ironclad', + clientId: env.IRONCLAD_CLIENT_ID as string, + clientSecret: env.IRONCLAD_CLIENT_SECRET as string, + authorizationUrl: 'https://na1.ironcladapp.com/oauth/authorize', + tokenUrl: 'https://na1.ironcladapp.com/oauth/token', + userInfoUrl: 'https://na1.ironcladapp.com/oauth/userinfo', + scopes: getCanonicalScopesForProvider('ironclad'), + responseType: 'code', + pkce: false, + accessType: 'offline', + prompt: 'consent', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/ironclad`, + getUserInfo: async (tokens) => { + try { + const response = await fetch('https://na1.ironcladapp.com/oauth/userinfo', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + + if (!response.ok) { + await response.text().catch(() => {}) + logger.error('Error fetching Ironclad user info:', { + status: response.status, + statusText: response.statusText, + }) + return null + } + + const data = await response.json() + const now = new Date() + + return { + id: `${data.id.toString()}-${crypto.randomUUID()}`, + name: data.username || data.email?.split('@')[0] || 'Ironclad User', + email: data.email || `${data.id}@ironclad.user`, + emailVerified: !!data.email, + createdAt: now, + updatedAt: now, + } + } catch (error) { + logger.error('Error in Ironclad getUserInfo:', { error }) + return null + } + }, + }, + // Notion provider { providerId: 'notion', diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 65ac812ec86..c340ec3c6d9 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -258,6 +258,8 @@ export const env = createEnv({ ASANA_CLIENT_SECRET: z.string().optional(), // Asana OAuth client secret AIRTABLE_CLIENT_ID: z.string().optional(), // Airtable OAuth client ID AIRTABLE_CLIENT_SECRET: z.string().optional(), // Airtable OAuth client secret + IRONCLAD_CLIENT_ID: z.string().optional(), // Ironclad OAuth client ID + IRONCLAD_CLIENT_SECRET: z.string().optional(), // Ironclad OAuth client secret APOLLO_API_KEY: z.string().optional(), // Apollo API key (optional system-wide config) SUPABASE_CLIENT_ID: z.string().optional(), // Supabase OAuth client ID SUPABASE_CLIENT_SECRET: z.string().optional(), // Supabase OAuth client secret diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 31f7d290fc4..c65341b1c98 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -23,6 +23,7 @@ import { GoogleSheetsIcon, GoogleTasksIcon, HubspotIcon, + IroncladIcon, JiraIcon, LinearIcon, LinkedInIcon, @@ -562,6 +563,32 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'airtable', }, + ironclad: { + name: 'Ironclad', + icon: IroncladIcon, + services: { + ironclad: { + name: 'Ironclad', + description: 'Manage contracts, workflows, and records in Ironclad.', + providerId: 'ironclad', + icon: IroncladIcon, + baseProviderIcon: IroncladIcon, + scopes: [ + 'public.workflows.readWorkflows', + 'public.workflows.createWorkflows', + 'public.workflows.updateWorkflows', + 'public.workflows.cancel', + 'public.workflows.readApprovals', + 'public.workflows.readComments', + 'public.workflows.createComments', + 'public.records.readRecords', + 'public.records.createRecords', + 'public.records.updateRecords', + ], + }, + }, + defaultService: 'ironclad', + }, notion: { name: 'Notion', icon: NotionIcon, @@ -1104,6 +1131,18 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: true, } } + case 'ironclad': { + const { clientId, clientSecret } = getCredentials( + env.IRONCLAD_CLIENT_ID, + env.IRONCLAD_CLIENT_SECRET + ) + return { + tokenEndpoint: 'https://na1.ironcladapp.com/oauth/token', + clientId, + clientSecret, + useBasicAuth: false, + } + } case 'notion': { const { clientId, clientSecret } = getCredentials( env.NOTION_CLIENT_ID, diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 60377544ecc..fb1077c16f7 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -127,6 +127,18 @@ export const SCOPE_DESCRIPTIONS: Record = { 'schema.bases:read': 'View bases and tables', 'webhook:manage': 'Manage webhooks', + // Ironclad scopes + 'public.workflows.readWorkflows': 'Read workflows', + 'public.workflows.createWorkflows': 'Create workflows', + 'public.workflows.updateWorkflows': 'Update workflows', + 'public.workflows.cancel': 'Cancel workflows', + 'public.workflows.readApprovals': 'Read workflow approvals', + 'public.workflows.readComments': 'Read workflow comments', + 'public.workflows.createComments': 'Create workflow comments', + 'public.records.readRecords': 'Read records', + 'public.records.createRecords': 'Create records', + 'public.records.updateRecords': 'Update records', + // Jira scopes 'read:jira-user': 'Read Jira user', 'read:jira-work': 'Read Jira work', diff --git a/apps/sim/tools/ironclad/add_comment.ts b/apps/sim/tools/ironclad/add_comment.ts new file mode 100644 index 00000000000..08a6212bfef --- /dev/null +++ b/apps/sim/tools/ironclad/add_comment.ts @@ -0,0 +1,71 @@ +import type { IroncladAddCommentParams, IroncladAddCommentResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const addCommentTool: ToolConfig = { + id: 'ironclad_add_comment', + name: 'Ironclad Add Comment', + description: 'Add a comment to a workflow.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + workflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + comment: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The comment text to add', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/comments`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ + comment: params.comment, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error( + (data as Record).message || + (data as Record).error || + 'Failed to add comment' + ) + } + + return { + success: true, + output: { + success: true, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the comment was added successfully' }, + }, +} diff --git a/apps/sim/tools/ironclad/cancel_workflow.ts b/apps/sim/tools/ironclad/cancel_workflow.ts new file mode 100644 index 00000000000..c222493d934 --- /dev/null +++ b/apps/sim/tools/ironclad/cancel_workflow.ts @@ -0,0 +1,68 @@ +import type { + IroncladCancelWorkflowParams, + IroncladCancelWorkflowResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const cancelWorkflowTool: ToolConfig< + IroncladCancelWorkflowParams, + IroncladCancelWorkflowResponse +> = { + id: 'ironclad_cancel_workflow', + name: 'Ironclad Cancel Workflow', + description: 'Cancel a workflow instance by its ID.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + workflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow to cancel', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/cancel`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error( + (data as Record).message || + (data as Record).error || + 'Failed to cancel workflow' + ) + } + + return { + success: true, + output: { + success: true, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the cancellation was successful' }, + }, +} diff --git a/apps/sim/tools/ironclad/create_record.ts b/apps/sim/tools/ironclad/create_record.ts new file mode 100644 index 00000000000..ec6975e5cad --- /dev/null +++ b/apps/sim/tools/ironclad/create_record.ts @@ -0,0 +1,100 @@ +import type { + IroncladCreateRecordParams, + IroncladCreateRecordResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const createRecordTool: ToolConfig< + IroncladCreateRecordParams, + IroncladCreateRecordResponse +> = { + id: 'ironclad_create_record', + name: 'Ironclad Create Record', + description: 'Create a new record in Ironclad with a specified type, name, and properties.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + recordType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The record type (e.g., "contract", "Statement of Work")', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'A human-readable name for the record', + }, + properties: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'JSON object of properties. Each property has a "type" (string/number/email/date/monetary_amount) and "value".', + }, + links: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of linked record objects, each with a "recordId" field', + }, + }, + + request: { + url: () => 'https://na1.ironcladapp.com/public/api/v1/records', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + type: params.recordType, + name: params.name, + } + if (params.properties) { + body.properties = JSON.parse(params.properties) + } + if (params.links) { + body.links = JSON.parse(params.links) + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to create record') + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + type: data.type ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The ID of the created record' }, + name: { type: 'string', description: 'The name of the record' }, + type: { type: 'string', description: 'The type of the record' }, + }, +} diff --git a/apps/sim/tools/ironclad/create_workflow.ts b/apps/sim/tools/ironclad/create_workflow.ts new file mode 100644 index 00000000000..23f3008bc95 --- /dev/null +++ b/apps/sim/tools/ironclad/create_workflow.ts @@ -0,0 +1,85 @@ +import type { + IroncladCreateWorkflowParams, + IroncladCreateWorkflowResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const createWorkflowTool: ToolConfig< + IroncladCreateWorkflowParams, + IroncladCreateWorkflowResponse +> = { + id: 'ironclad_create_workflow', + name: 'Ironclad Create Workflow', + description: 'Create a new workflow in Ironclad using a specified template and attributes.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + template: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The template ID to use for the workflow', + }, + attributes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON object of workflow attributes', + }, + }, + + request: { + url: () => 'https://na1.ironcladapp.com/public/api/v1/workflows', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => { + const body: Record = { + template: params.template, + } + if (params.attributes) { + body.attributes = JSON.parse(params.attributes) + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to create workflow') + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? null, + template: data.template ?? null, + creator: data.creator ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The ID of the created workflow' }, + status: { type: 'string', description: 'The status of the workflow' }, + template: { type: 'string', description: 'The template used for the workflow' }, + creator: { type: 'string', description: 'The creator of the workflow' }, + }, +} diff --git a/apps/sim/tools/ironclad/get_record.ts b/apps/sim/tools/ironclad/get_record.ts new file mode 100644 index 00000000000..f7524ae9178 --- /dev/null +++ b/apps/sim/tools/ironclad/get_record.ts @@ -0,0 +1,67 @@ +import type { IroncladGetRecordParams, IroncladGetRecordResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const getRecordTool: ToolConfig = { + id: 'ironclad_get_record', + name: 'Ironclad Get Record', + description: 'Retrieve details of a specific record by its ID.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + recordId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the record', + }, + }, + + request: { + url: (params) => `https://na1.ironcladapp.com/public/api/v1/records/${params.recordId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get record') + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? null, + type: data.type ?? null, + properties: data.properties ?? null, + createdAt: data.createdAt ?? null, + updatedAt: data.updatedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The record ID' }, + name: { type: 'string', description: 'The record name' }, + type: { type: 'string', description: 'The record type' }, + properties: { type: 'json', description: 'The record properties' }, + createdAt: { type: 'string', description: 'When the record was created' }, + updatedAt: { type: 'string', description: 'When the record was last updated' }, + }, +} diff --git a/apps/sim/tools/ironclad/get_workflow.ts b/apps/sim/tools/ironclad/get_workflow.ts new file mode 100644 index 00000000000..95bfc32f559 --- /dev/null +++ b/apps/sim/tools/ironclad/get_workflow.ts @@ -0,0 +1,68 @@ +import type { IroncladGetWorkflowParams, IroncladGetWorkflowResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const getWorkflowTool: ToolConfig = { + id: 'ironclad_get_workflow', + name: 'Ironclad Get Workflow', + description: 'Retrieve details of a specific workflow by its ID.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + workflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get workflow') + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? null, + template: data.template ?? null, + creator: data.creator ?? null, + step: data.step ?? null, + attributes: data.attributes ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The workflow ID' }, + status: { type: 'string', description: 'The workflow status' }, + template: { type: 'string', description: 'The template used for the workflow' }, + creator: { type: 'string', description: 'The creator of the workflow' }, + step: { type: 'string', description: 'The current step of the workflow' }, + attributes: { type: 'json', description: 'The workflow attributes' }, + }, +} diff --git a/apps/sim/tools/ironclad/index.ts b/apps/sim/tools/ironclad/index.ts new file mode 100644 index 00000000000..10be8772a20 --- /dev/null +++ b/apps/sim/tools/ironclad/index.ts @@ -0,0 +1,27 @@ +import { addCommentTool } from '@/tools/ironclad/add_comment' +import { cancelWorkflowTool } from '@/tools/ironclad/cancel_workflow' +import { createRecordTool } from '@/tools/ironclad/create_record' +import { createWorkflowTool } from '@/tools/ironclad/create_workflow' +import { getRecordTool } from '@/tools/ironclad/get_record' +import { getWorkflowTool } from '@/tools/ironclad/get_workflow' +import { listRecordsTool } from '@/tools/ironclad/list_records' +import { listWorkflowApprovalsTool } from '@/tools/ironclad/list_workflow_approvals' +import { listWorkflowCommentsTool } from '@/tools/ironclad/list_workflow_comments' +import { listWorkflowsTool } from '@/tools/ironclad/list_workflows' +import { updateRecordTool } from '@/tools/ironclad/update_record' +import { updateWorkflowMetadataTool } from '@/tools/ironclad/update_workflow_metadata' + +export const ironcladCreateWorkflowTool = createWorkflowTool +export const ironcladListWorkflowsTool = listWorkflowsTool +export const ironcladGetWorkflowTool = getWorkflowTool +export const ironcladUpdateWorkflowMetadataTool = updateWorkflowMetadataTool +export const ironcladCancelWorkflowTool = cancelWorkflowTool +export const ironcladListWorkflowApprovalsTool = listWorkflowApprovalsTool +export const ironcladAddCommentTool = addCommentTool +export const ironcladListWorkflowCommentsTool = listWorkflowCommentsTool +export const ironcladCreateRecordTool = createRecordTool +export const ironcladListRecordsTool = listRecordsTool +export const ironcladGetRecordTool = getRecordTool +export const ironcladUpdateRecordTool = updateRecordTool + +export * from './types' diff --git a/apps/sim/tools/ironclad/list_records.ts b/apps/sim/tools/ironclad/list_records.ts new file mode 100644 index 00000000000..bc6f0d89249 --- /dev/null +++ b/apps/sim/tools/ironclad/list_records.ts @@ -0,0 +1,90 @@ +import type { IroncladListRecordsParams, IroncladListRecordsResponse } from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listRecordsTool: ToolConfig = { + id: 'ironclad_list_records', + name: 'Ironclad List Records', + description: + 'List all records in Ironclad with pagination and optional filtering by last updated time.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number (starting from 0)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + }, + lastUpdated: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter records updated on or after this ISO 8601 timestamp', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://na1.ironcladapp.com/public/api/v1/records') + if (params.page !== undefined) url.searchParams.set('page', String(params.page)) + if (params.pageSize !== undefined) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.lastUpdated) url.searchParams.set('lastUpdated', params.lastUpdated) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list records') + } + + const records = (data.list ?? []).map((r: Record) => ({ + id: (r.id as string) ?? '', + name: (r.name as string) ?? null, + type: (r.type as string) ?? null, + createdAt: (r.createdAt as string) ?? null, + updatedAt: (r.updatedAt as string) ?? null, + })) + + return { + success: true, + output: { + records, + page: data.page ?? 0, + pageSize: data.pageSize ?? 20, + count: data.count ?? 0, + }, + } + }, + + outputs: { + records: { type: 'json', description: 'List of records' }, + page: { type: 'number', description: 'Current page number' }, + pageSize: { type: 'number', description: 'Number of results per page' }, + count: { type: 'number', description: 'Total number of records' }, + }, +} diff --git a/apps/sim/tools/ironclad/list_workflow_approvals.ts b/apps/sim/tools/ironclad/list_workflow_approvals.ts new file mode 100644 index 00000000000..ea5998ae394 --- /dev/null +++ b/apps/sim/tools/ironclad/list_workflow_approvals.ts @@ -0,0 +1,65 @@ +import type { + IroncladListWorkflowApprovalsParams, + IroncladListWorkflowApprovalsResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowApprovalsTool: ToolConfig< + IroncladListWorkflowApprovalsParams, + IroncladListWorkflowApprovalsResponse +> = { + id: 'ironclad_list_workflow_approvals', + name: 'Ironclad List Workflow Approvals', + description: + 'List all triggered approvals for a specific workflow. Conditional approvals that have not been triggered will not appear.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + workflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/approvals`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list workflow approvals') + } + + return { + success: true, + output: { + approvals: data.approvalGroups ?? data ?? [], + }, + } + }, + + outputs: { + approvals: { type: 'json', description: 'List of triggered approval groups for the workflow' }, + }, +} diff --git a/apps/sim/tools/ironclad/list_workflow_comments.ts b/apps/sim/tools/ironclad/list_workflow_comments.ts new file mode 100644 index 00000000000..ec723528862 --- /dev/null +++ b/apps/sim/tools/ironclad/list_workflow_comments.ts @@ -0,0 +1,64 @@ +import type { + IroncladListWorkflowCommentsParams, + IroncladListWorkflowCommentsResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowCommentsTool: ToolConfig< + IroncladListWorkflowCommentsParams, + IroncladListWorkflowCommentsResponse +> = { + id: 'ironclad_list_workflow_comments', + name: 'Ironclad List Workflow Comments', + description: 'List all comments on a workflow.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + workflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/comments`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list workflow comments') + } + + return { + success: true, + output: { + comments: data ?? [], + }, + } + }, + + outputs: { + comments: { type: 'json', description: 'List of comments on the workflow' }, + }, +} diff --git a/apps/sim/tools/ironclad/list_workflows.ts b/apps/sim/tools/ironclad/list_workflows.ts new file mode 100644 index 00000000000..a3b90ceea19 --- /dev/null +++ b/apps/sim/tools/ironclad/list_workflows.ts @@ -0,0 +1,87 @@ +import type { + IroncladListWorkflowsParams, + IroncladListWorkflowsResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowsTool: ToolConfig< + IroncladListWorkflowsParams, + IroncladListWorkflowsResponse +> = { + id: 'ironclad_list_workflows', + name: 'Ironclad List Workflows', + description: 'List all workflows in Ironclad with pagination support.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number (starting from 0)', + }, + perPage: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://na1.ironcladapp.com/public/api/v1/workflows') + if (params.page !== undefined) url.searchParams.set('page', String(params.page)) + if (params.perPage !== undefined) url.searchParams.set('perPage', String(params.perPage)) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to list workflows') + } + + const workflows = (data.list ?? []).map((w: Record) => ({ + id: (w.id as string) ?? '', + status: (w.status as string) ?? null, + template: (w.template as string) ?? null, + creator: (w.creator as string) ?? null, + })) + + return { + success: true, + output: { + workflows, + page: data.page ?? 0, + pageSize: data.pageSize ?? 20, + count: data.count ?? 0, + }, + } + }, + + outputs: { + workflows: { type: 'json', description: 'List of workflows' }, + page: { type: 'number', description: 'Current page number' }, + pageSize: { type: 'number', description: 'Number of results per page' }, + count: { type: 'number', description: 'Total number of workflows' }, + }, +} diff --git a/apps/sim/tools/ironclad/types.ts b/apps/sim/tools/ironclad/types.ts new file mode 100644 index 00000000000..d97c3549f58 --- /dev/null +++ b/apps/sim/tools/ironclad/types.ts @@ -0,0 +1,199 @@ +import type { ToolResponse } from '@/tools/types' + +/** Base parameters shared by all Ironclad tools */ +export interface IroncladBaseParams { + accessToken: string +} + +/** Create Workflow params */ +export interface IroncladCreateWorkflowParams extends IroncladBaseParams { + template: string + attributes?: string +} + +/** Create Workflow response */ +export interface IroncladCreateWorkflowResponse extends ToolResponse { + output: { + id: string + status: string | null + template: string | null + creator: string | null + } +} + +/** List Workflows params */ +export interface IroncladListWorkflowsParams extends IroncladBaseParams { + page?: number + perPage?: number +} + +/** List Workflows response */ +export interface IroncladListWorkflowsResponse extends ToolResponse { + output: { + workflows: IroncladWorkflowSummary[] + page: number + pageSize: number + count: number + } +} + +/** Get Workflow params */ +export interface IroncladGetWorkflowParams extends IroncladBaseParams { + workflowId: string +} + +/** Get Workflow response */ +export interface IroncladGetWorkflowResponse extends ToolResponse { + output: { + id: string + status: string | null + template: string | null + creator: string | null + step: string | null + attributes: Record | null + } +} + +/** Update Workflow Metadata params */ +export interface IroncladUpdateWorkflowMetadataParams extends IroncladBaseParams { + workflowId: string + actions: string +} + +/** Update Workflow Metadata response */ +export interface IroncladUpdateWorkflowMetadataResponse extends ToolResponse { + output: { + success: boolean + } +} + +/** Create Record params */ +export interface IroncladCreateRecordParams extends IroncladBaseParams { + recordType: string + name: string + properties?: string + links?: string +} + +/** Create Record response */ +export interface IroncladCreateRecordResponse extends ToolResponse { + output: { + id: string + name: string + type: string | null + } +} + +/** List Records params */ +export interface IroncladListRecordsParams extends IroncladBaseParams { + page?: number + pageSize?: number + lastUpdated?: string +} + +/** List Records response */ +export interface IroncladListRecordsResponse extends ToolResponse { + output: { + records: IroncladRecordSummary[] + page: number + pageSize: number + count: number + } +} + +/** Get Record params */ +export interface IroncladGetRecordParams extends IroncladBaseParams { + recordId: string +} + +/** Get Record response */ +export interface IroncladGetRecordResponse extends ToolResponse { + output: { + id: string + name: string | null + type: string | null + properties: Record | null + createdAt: string | null + updatedAt: string | null + } +} + +/** Update Record params */ +export interface IroncladUpdateRecordParams extends IroncladBaseParams { + recordId: string + properties: string +} + +/** Update Record response */ +export interface IroncladUpdateRecordResponse extends ToolResponse { + output: { + id: string + name: string | null + type: string | null + } +} + +/** Cancel Workflow params */ +export interface IroncladCancelWorkflowParams extends IroncladBaseParams { + workflowId: string +} + +/** Cancel Workflow response */ +export interface IroncladCancelWorkflowResponse extends ToolResponse { + output: { + success: boolean + } +} + +/** List Workflow Approvals params */ +export interface IroncladListWorkflowApprovalsParams extends IroncladBaseParams { + workflowId: string +} + +/** List Workflow Approvals response */ +export interface IroncladListWorkflowApprovalsResponse extends ToolResponse { + output: { + approvals: unknown[] + } +} + +/** Add Comment params */ +export interface IroncladAddCommentParams extends IroncladBaseParams { + workflowId: string + comment: string +} + +/** Add Comment response */ +export interface IroncladAddCommentResponse extends ToolResponse { + output: { + success: boolean + } +} + +/** List Workflow Comments params */ +export interface IroncladListWorkflowCommentsParams extends IroncladBaseParams { + workflowId: string +} + +/** List Workflow Comments response */ +export interface IroncladListWorkflowCommentsResponse extends ToolResponse { + output: { + comments: unknown[] + } +} + +/** Shared summary types */ +export interface IroncladWorkflowSummary { + id: string + status: string | null + template: string | null + creator: string | null +} + +export interface IroncladRecordSummary { + id: string + name: string | null + type: string | null + createdAt: string | null + updatedAt: string | null +} diff --git a/apps/sim/tools/ironclad/update_record.ts b/apps/sim/tools/ironclad/update_record.ts new file mode 100644 index 00000000000..42bae3b576b --- /dev/null +++ b/apps/sim/tools/ironclad/update_record.ts @@ -0,0 +1,75 @@ +import type { + IroncladUpdateRecordParams, + IroncladUpdateRecordResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const updateRecordTool: ToolConfig< + IroncladUpdateRecordParams, + IroncladUpdateRecordResponse +> = { + id: 'ironclad_update_record', + name: 'Ironclad Update Record', + description: 'Update metadata fields on an existing record.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + recordId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the record to update', + }, + properties: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'JSON object of fields to update (e.g., {"fieldName": "newValue"})', + }, + }, + + request: { + url: (params) => `https://na1.ironcladapp.com/public/api/v1/records/${params.recordId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => JSON.parse(params.properties), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to update record') + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? null, + type: data.type ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'The record ID' }, + name: { type: 'string', description: 'The record name' }, + type: { type: 'string', description: 'The record type' }, + }, +} diff --git a/apps/sim/tools/ironclad/update_workflow_metadata.ts b/apps/sim/tools/ironclad/update_workflow_metadata.ts new file mode 100644 index 00000000000..1a55d3abcc4 --- /dev/null +++ b/apps/sim/tools/ironclad/update_workflow_metadata.ts @@ -0,0 +1,75 @@ +import type { + IroncladUpdateWorkflowMetadataParams, + IroncladUpdateWorkflowMetadataResponse, +} from '@/tools/ironclad/types' +import type { ToolConfig } from '@/tools/types' + +export const updateWorkflowMetadataTool: ToolConfig< + IroncladUpdateWorkflowMetadataParams, + IroncladUpdateWorkflowMetadataResponse +> = { + id: 'ironclad_update_workflow_metadata', + name: 'Ironclad Update Workflow Metadata', + description: + 'Update attributes on a workflow. The workflow must be in the Review step. Supports set and remove actions.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ironclad', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + workflowId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique identifier of the workflow', + }, + actions: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'JSON array of actions. Each action has "action" (set/remove), "field", and optionally "value".', + }, + }, + + request: { + url: (params) => + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/attributes`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }), + body: (params) => ({ + actions: JSON.parse(params.actions), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json() + throw new Error(data.message || data.error || 'Failed to update workflow metadata') + } + + return { + success: true, + output: { + success: true, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the update was successful' }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 99efb6dcbbc..3469d891c64 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1172,6 +1172,20 @@ import { intercomUpdateContactV2Tool, intercomUpdateTicketV2Tool, } from '@/tools/intercom' +import { + ironcladAddCommentTool, + ironcladCancelWorkflowTool, + ironcladCreateRecordTool, + ironcladCreateWorkflowTool, + ironcladGetRecordTool, + ironcladGetWorkflowTool, + ironcladListRecordsTool, + ironcladListWorkflowApprovalsTool, + ironcladListWorkflowCommentsTool, + ironcladListWorkflowsTool, + ironcladUpdateRecordTool, + ironcladUpdateWorkflowMetadataTool, +} from '@/tools/ironclad' import { jinaReadUrlTool, jinaSearchTool } from '@/tools/jina' import { jiraAddAttachmentTool, @@ -4696,6 +4710,18 @@ export const tools: Record = { intercom_create_event_v2: intercomCreateEventV2Tool, intercom_attach_contact_to_company_v2: intercomAttachContactToCompanyV2Tool, intercom_detach_contact_from_company_v2: intercomDetachContactFromCompanyV2Tool, + ironclad_add_comment: ironcladAddCommentTool, + ironclad_cancel_workflow: ironcladCancelWorkflowTool, + ironclad_create_record: ironcladCreateRecordTool, + ironclad_create_workflow: ironcladCreateWorkflowTool, + ironclad_get_record: ironcladGetRecordTool, + ironclad_get_workflow: ironcladGetWorkflowTool, + ironclad_list_records: ironcladListRecordsTool, + ironclad_list_workflow_approvals: ironcladListWorkflowApprovalsTool, + ironclad_list_workflow_comments: ironcladListWorkflowCommentsTool, + ironclad_list_workflows: ironcladListWorkflowsTool, + ironclad_update_record: ironcladUpdateRecordTool, + ironclad_update_workflow_metadata: ironcladUpdateWorkflowMetadataTool, sentry_issues_list: listIssuesTool, sentry_issues_get: getIssueTool, sentry_issues_update: updateIssueTool, From eca34502bb971d9aa55cbd758c2ee3d294c193ba Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 15:33:37 -0700 Subject: [PATCH 2/8] fix(ironclad): address PR review comments - add required constraint, defensive response parsing --- apps/sim/blocks/blocks/ironclad.ts | 1 + apps/sim/tools/ironclad/list_workflow_comments.ts | 2 +- apps/sim/tools/ironclad/list_workflows.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/sim/blocks/blocks/ironclad.ts b/apps/sim/blocks/blocks/ironclad.ts index 5d173f73d23..58e7d92bbf8 100644 --- a/apps/sim/blocks/blocks/ironclad.ts +++ b/apps/sim/blocks/blocks/ironclad.ts @@ -163,6 +163,7 @@ export const IroncladBlock: BlockConfig = { type: 'long-input', placeholder: '{"counterpartyName": {"type": "string", "value": "Acme Corp"}}', condition: { field: 'operation', value: ['create_record', 'update_record'] }, + required: { field: 'operation', value: 'update_record' }, wandConfig: { enabled: true, prompt: diff --git a/apps/sim/tools/ironclad/list_workflow_comments.ts b/apps/sim/tools/ironclad/list_workflow_comments.ts index ec723528862..1a06b483e28 100644 --- a/apps/sim/tools/ironclad/list_workflow_comments.ts +++ b/apps/sim/tools/ironclad/list_workflow_comments.ts @@ -53,7 +53,7 @@ export const listWorkflowCommentsTool: ToolConfig< return { success: true, output: { - comments: data ?? [], + comments: data.list ?? data.comments ?? data ?? [], }, } }, diff --git a/apps/sim/tools/ironclad/list_workflows.ts b/apps/sim/tools/ironclad/list_workflows.ts index a3b90ceea19..353ea193cff 100644 --- a/apps/sim/tools/ironclad/list_workflows.ts +++ b/apps/sim/tools/ironclad/list_workflows.ts @@ -72,7 +72,7 @@ export const listWorkflowsTool: ToolConfig< output: { workflows, page: data.page ?? 0, - pageSize: data.pageSize ?? 20, + pageSize: data.pageSize ?? data.perPage ?? 20, count: data.count ?? 0, }, } From 13e8ea3e59ea9a9d8d9498e5904ac42e1a247162 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 15:41:25 -0700 Subject: [PATCH 3/8] fix(ironclad): add ironclad to OAuthProvider and OAuthService types --- apps/sim/lib/oauth/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index eab51d7d34b..599555dbb45 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -42,6 +42,7 @@ export type OAuthProvider = | 'attio' | 'pipedrive' | 'hubspot' + | 'ironclad' | 'salesforce' | 'linkedin' | 'shopify' @@ -92,6 +93,7 @@ export type OAuthService = | 'attio' | 'pipedrive' | 'hubspot' + | 'ironclad' | 'salesforce' | 'linkedin' | 'shopify' From 8e68483ee7096b6aabaf92cf79632dcafda3841e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 15:42:41 -0700 Subject: [PATCH 4/8] fix(ironclad): add JSON.parse error handling and .catch on error response --- apps/sim/tools/ironclad/create_record.ts | 12 ++++++++++-- apps/sim/tools/ironclad/create_workflow.ts | 6 +++++- apps/sim/tools/ironclad/update_record.ts | 8 +++++++- .../tools/ironclad/update_workflow_metadata.ts | 18 +++++++++++++----- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/apps/sim/tools/ironclad/create_record.ts b/apps/sim/tools/ironclad/create_record.ts index ec6975e5cad..77975c12a22 100644 --- a/apps/sim/tools/ironclad/create_record.ts +++ b/apps/sim/tools/ironclad/create_record.ts @@ -66,10 +66,18 @@ export const createRecordTool: ToolConfig< name: params.name, } if (params.properties) { - body.properties = JSON.parse(params.properties) + try { + body.properties = JSON.parse(params.properties) + } catch { + throw new Error('Invalid JSON in properties field') + } } if (params.links) { - body.links = JSON.parse(params.links) + try { + body.links = JSON.parse(params.links) + } catch { + throw new Error('Invalid JSON in links field') + } } return body }, diff --git a/apps/sim/tools/ironclad/create_workflow.ts b/apps/sim/tools/ironclad/create_workflow.ts index 23f3008bc95..cec3cc314fb 100644 --- a/apps/sim/tools/ironclad/create_workflow.ts +++ b/apps/sim/tools/ironclad/create_workflow.ts @@ -52,7 +52,11 @@ export const createWorkflowTool: ToolConfig< template: params.template, } if (params.attributes) { - body.attributes = JSON.parse(params.attributes) + try { + body.attributes = JSON.parse(params.attributes) + } catch { + throw new Error('Invalid JSON in attributes field') + } } return body }, diff --git a/apps/sim/tools/ironclad/update_record.ts b/apps/sim/tools/ironclad/update_record.ts index 42bae3b576b..8a5e185ae01 100644 --- a/apps/sim/tools/ironclad/update_record.ts +++ b/apps/sim/tools/ironclad/update_record.ts @@ -47,7 +47,13 @@ export const updateRecordTool: ToolConfig< 'Content-Type': 'application/json', Accept: 'application/json', }), - body: (params) => JSON.parse(params.properties), + body: (params) => { + try { + return JSON.parse(params.properties) + } catch { + throw new Error('Invalid JSON in properties field') + } + }, }, transformResponse: async (response: Response) => { diff --git a/apps/sim/tools/ironclad/update_workflow_metadata.ts b/apps/sim/tools/ironclad/update_workflow_metadata.ts index 1a55d3abcc4..d2dbe084cf8 100644 --- a/apps/sim/tools/ironclad/update_workflow_metadata.ts +++ b/apps/sim/tools/ironclad/update_workflow_metadata.ts @@ -50,15 +50,23 @@ export const updateWorkflowMetadataTool: ToolConfig< 'Content-Type': 'application/json', Accept: 'application/json', }), - body: (params) => ({ - actions: JSON.parse(params.actions), - }), + body: (params) => { + try { + return { actions: JSON.parse(params.actions) } + } catch { + throw new Error('Invalid JSON in actions field') + } + }, }, transformResponse: async (response: Response) => { if (!response.ok) { - const data = await response.json() - throw new Error(data.message || data.error || 'Failed to update workflow metadata') + const data = await response.json().catch(() => ({})) + throw new Error( + (data as Record).message || + (data as Record).error || + 'Failed to update workflow metadata' + ) } return { From 800cfb072f86e4b57a2ff960aed17f2305b44778 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 15:46:28 -0700 Subject: [PATCH 5/8] fix(ironclad): add supportsRefreshTokenRotation to token refresh config --- apps/sim/lib/oauth/oauth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index c65341b1c98..61aee575337 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -1141,6 +1141,7 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { clientId, clientSecret, useBasicAuth: false, + supportsRefreshTokenRotation: true, } } case 'notion': { From e7764b86c80ed9aafef255ddf5b2bc23a3024320 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 16:07:49 -0700 Subject: [PATCH 6/8] fix(ironclad): use !== undefined for page=0 guard in block config --- apps/sim/blocks/blocks/ironclad.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/blocks/blocks/ironclad.ts b/apps/sim/blocks/blocks/ironclad.ts index 58e7d92bbf8..fa076d87ef3 100644 --- a/apps/sim/blocks/blocks/ironclad.ts +++ b/apps/sim/blocks/blocks/ironclad.ts @@ -247,8 +247,8 @@ export const IroncladBlock: BlockConfig = { credential: oauthCredential, ...rest, } - if (params.page) result.page = Number(params.page) - if (params.pageSize) { + if (params.page !== undefined) result.page = Number(params.page) + if (params.pageSize !== undefined && params.pageSize !== '') { if (params.operation === 'list_workflows') { result.perPage = Number(params.pageSize) result.pageSize = undefined From fd1182fc208995a33284d3009365f6fb9376adb9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 16:11:56 -0700 Subject: [PATCH 7/8] fix(ironclad): rename workflowId param to avoid framework collision --- apps/sim/blocks/blocks/ironclad.ts | 4 ++++ apps/sim/tools/ironclad/add_comment.ts | 4 ++-- apps/sim/tools/ironclad/cancel_workflow.ts | 4 ++-- apps/sim/tools/ironclad/get_workflow.ts | 4 ++-- apps/sim/tools/ironclad/list_workflow_approvals.ts | 4 ++-- apps/sim/tools/ironclad/list_workflow_comments.ts | 4 ++-- apps/sim/tools/ironclad/types.ts | 12 ++++++------ apps/sim/tools/ironclad/update_workflow_metadata.ts | 4 ++-- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/apps/sim/blocks/blocks/ironclad.ts b/apps/sim/blocks/blocks/ironclad.ts index fa076d87ef3..a5ae32278c6 100644 --- a/apps/sim/blocks/blocks/ironclad.ts +++ b/apps/sim/blocks/blocks/ironclad.ts @@ -247,6 +247,10 @@ export const IroncladBlock: BlockConfig = { credential: oauthCredential, ...rest, } + if (result.workflowId !== undefined) { + result.ironcladWorkflowId = result.workflowId + delete result.workflowId + } if (params.page !== undefined) result.page = Number(params.page) if (params.pageSize !== undefined && params.pageSize !== '') { if (params.operation === 'list_workflows') { diff --git a/apps/sim/tools/ironclad/add_comment.ts b/apps/sim/tools/ironclad/add_comment.ts index 08a6212bfef..fcb4e91cd2f 100644 --- a/apps/sim/tools/ironclad/add_comment.ts +++ b/apps/sim/tools/ironclad/add_comment.ts @@ -19,7 +19,7 @@ export const addCommentTool: ToolConfig - `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/comments`, + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/comments`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/ironclad/cancel_workflow.ts b/apps/sim/tools/ironclad/cancel_workflow.ts index c222493d934..34127d80510 100644 --- a/apps/sim/tools/ironclad/cancel_workflow.ts +++ b/apps/sim/tools/ironclad/cancel_workflow.ts @@ -25,7 +25,7 @@ export const cancelWorkflowTool: ToolConfig< visibility: 'hidden', description: 'OAuth access token', }, - workflowId: { + ironcladWorkflowId: { type: 'string', required: true, visibility: 'user-or-llm', @@ -35,7 +35,7 @@ export const cancelWorkflowTool: ToolConfig< request: { url: (params) => - `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/cancel`, + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/cancel`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/ironclad/get_workflow.ts b/apps/sim/tools/ironclad/get_workflow.ts index 95bfc32f559..9433498111f 100644 --- a/apps/sim/tools/ironclad/get_workflow.ts +++ b/apps/sim/tools/ironclad/get_workflow.ts @@ -19,7 +19,7 @@ export const getWorkflowTool: ToolConfig - `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}`, + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/ironclad/list_workflow_approvals.ts b/apps/sim/tools/ironclad/list_workflow_approvals.ts index ea5998ae394..d968382ab3d 100644 --- a/apps/sim/tools/ironclad/list_workflow_approvals.ts +++ b/apps/sim/tools/ironclad/list_workflow_approvals.ts @@ -26,7 +26,7 @@ export const listWorkflowApprovalsTool: ToolConfig< visibility: 'hidden', description: 'OAuth access token', }, - workflowId: { + ironcladWorkflowId: { type: 'string', required: true, visibility: 'user-or-llm', @@ -36,7 +36,7 @@ export const listWorkflowApprovalsTool: ToolConfig< request: { url: (params) => - `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/approvals`, + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/approvals`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/ironclad/list_workflow_comments.ts b/apps/sim/tools/ironclad/list_workflow_comments.ts index 1a06b483e28..dc4baad74f6 100644 --- a/apps/sim/tools/ironclad/list_workflow_comments.ts +++ b/apps/sim/tools/ironclad/list_workflow_comments.ts @@ -25,7 +25,7 @@ export const listWorkflowCommentsTool: ToolConfig< visibility: 'hidden', description: 'OAuth access token', }, - workflowId: { + ironcladWorkflowId: { type: 'string', required: true, visibility: 'user-or-llm', @@ -35,7 +35,7 @@ export const listWorkflowCommentsTool: ToolConfig< request: { url: (params) => - `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/comments`, + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/comments`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/ironclad/types.ts b/apps/sim/tools/ironclad/types.ts index d97c3549f58..6ba57460863 100644 --- a/apps/sim/tools/ironclad/types.ts +++ b/apps/sim/tools/ironclad/types.ts @@ -39,7 +39,7 @@ export interface IroncladListWorkflowsResponse extends ToolResponse { /** Get Workflow params */ export interface IroncladGetWorkflowParams extends IroncladBaseParams { - workflowId: string + ironcladWorkflowId: string } /** Get Workflow response */ @@ -56,7 +56,7 @@ export interface IroncladGetWorkflowResponse extends ToolResponse { /** Update Workflow Metadata params */ export interface IroncladUpdateWorkflowMetadataParams extends IroncladBaseParams { - workflowId: string + ironcladWorkflowId: string actions: string } @@ -135,7 +135,7 @@ export interface IroncladUpdateRecordResponse extends ToolResponse { /** Cancel Workflow params */ export interface IroncladCancelWorkflowParams extends IroncladBaseParams { - workflowId: string + ironcladWorkflowId: string } /** Cancel Workflow response */ @@ -147,7 +147,7 @@ export interface IroncladCancelWorkflowResponse extends ToolResponse { /** List Workflow Approvals params */ export interface IroncladListWorkflowApprovalsParams extends IroncladBaseParams { - workflowId: string + ironcladWorkflowId: string } /** List Workflow Approvals response */ @@ -159,7 +159,7 @@ export interface IroncladListWorkflowApprovalsResponse extends ToolResponse { /** Add Comment params */ export interface IroncladAddCommentParams extends IroncladBaseParams { - workflowId: string + ironcladWorkflowId: string comment: string } @@ -172,7 +172,7 @@ export interface IroncladAddCommentResponse extends ToolResponse { /** List Workflow Comments params */ export interface IroncladListWorkflowCommentsParams extends IroncladBaseParams { - workflowId: string + ironcladWorkflowId: string } /** List Workflow Comments response */ diff --git a/apps/sim/tools/ironclad/update_workflow_metadata.ts b/apps/sim/tools/ironclad/update_workflow_metadata.ts index d2dbe084cf8..a19a382f480 100644 --- a/apps/sim/tools/ironclad/update_workflow_metadata.ts +++ b/apps/sim/tools/ironclad/update_workflow_metadata.ts @@ -26,7 +26,7 @@ export const updateWorkflowMetadataTool: ToolConfig< visibility: 'hidden', description: 'OAuth access token', }, - workflowId: { + ironcladWorkflowId: { type: 'string', required: true, visibility: 'user-or-llm', @@ -43,7 +43,7 @@ export const updateWorkflowMetadataTool: ToolConfig< request: { url: (params) => - `https://na1.ironcladapp.com/public/api/v1/workflows/${params.workflowId.trim()}/attributes`, + `https://na1.ironcladapp.com/public/api/v1/workflows/${params.ironcladWorkflowId.trim()}/attributes`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, From 5d7bad0b1cc88514fd4388f2bbafe736590b9781 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Mar 2026 16:18:04 -0700 Subject: [PATCH 8/8] lint --- apps/sim/blocks/blocks/ironclad.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/blocks/blocks/ironclad.ts b/apps/sim/blocks/blocks/ironclad.ts index a5ae32278c6..b0feff5ca35 100644 --- a/apps/sim/blocks/blocks/ironclad.ts +++ b/apps/sim/blocks/blocks/ironclad.ts @@ -249,7 +249,7 @@ export const IroncladBlock: BlockConfig = { } if (result.workflowId !== undefined) { result.ironcladWorkflowId = result.workflowId - delete result.workflowId + result.workflowId = undefined } if (params.page !== undefined) result.page = Number(params.page) if (params.pageSize !== undefined && params.pageSize !== '') {