From c893ec7d899a7833348b99ba9f354cb567b1ec6c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 18:17:53 -0700 Subject: [PATCH 01/16] feat(box): add Box and Box Sign integrations Add complete Box integration with file management (upload, download, get info, list folders, create/delete folders, copy, search, update metadata) and Box Sign e-signature support (create/get/list/cancel/resend sign requests). Includes OAuth provider setup, internal upload API route following the Dropbox pattern, block configurations, icon, and generated docs. Co-Authored-By: Claude Opus 4.6 --- apps/docs/components/icons.tsx | 2 +- apps/docs/components/ui/icon-mapping.ts | 3 + apps/docs/content/docs/en/tools/box.mdx | 280 ++++++++++++++ apps/docs/content/docs/en/tools/box_sign.mdx | 183 +++++++++ apps/docs/content/docs/en/tools/meta.json | 2 + apps/sim/app/api/tools/box/upload/route.ts | 140 +++++++ apps/sim/blocks/blocks/box.ts | 379 +++++++++++++++++++ apps/sim/blocks/blocks/box_sign.ts | 302 +++++++++++++++ apps/sim/blocks/registry.ts | 4 + apps/sim/lib/auth/auth.ts | 46 +++ apps/sim/lib/core/config/env.ts | 2 + apps/sim/lib/oauth/oauth.ts | 25 ++ apps/sim/lib/oauth/types.ts | 2 + apps/sim/tools/box/copy_file.ts | 82 ++++ apps/sim/tools/box/create_folder.ts | 71 ++++ apps/sim/tools/box/delete_file.ts | 57 +++ apps/sim/tools/box/delete_folder.ts | 68 ++++ apps/sim/tools/box/download_file.ts | 87 +++++ apps/sim/tools/box/get_file_info.ts | 87 +++++ apps/sim/tools/box/index.ts | 10 + apps/sim/tools/box/list_folder_items.ts | 98 +++++ apps/sim/tools/box/search.ts | 105 +++++ apps/sim/tools/box/types.ts | 251 ++++++++++++ apps/sim/tools/box/update_file.ts | 120 ++++++ apps/sim/tools/box/upload_file.ts | 78 ++++ apps/sim/tools/box_sign/cancel_request.ts | 91 +++++ apps/sim/tools/box_sign/create_request.ts | 220 +++++++++++ apps/sim/tools/box_sign/get_request.ts | 78 ++++ apps/sim/tools/box_sign/index.ts | 5 + apps/sim/tools/box_sign/list_requests.ts | 100 +++++ apps/sim/tools/box_sign/resend_request.ts | 55 +++ apps/sim/tools/box_sign/types.ts | 163 ++++++++ apps/sim/tools/registry.ts | 34 ++ 33 files changed, 3229 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/docs/en/tools/box.mdx create mode 100644 apps/docs/content/docs/en/tools/box_sign.mdx create mode 100644 apps/sim/app/api/tools/box/upload/route.ts create mode 100644 apps/sim/blocks/blocks/box.ts create mode 100644 apps/sim/blocks/blocks/box_sign.ts create mode 100644 apps/sim/tools/box/copy_file.ts create mode 100644 apps/sim/tools/box/create_folder.ts create mode 100644 apps/sim/tools/box/delete_file.ts create mode 100644 apps/sim/tools/box/delete_folder.ts create mode 100644 apps/sim/tools/box/download_file.ts create mode 100644 apps/sim/tools/box/get_file_info.ts create mode 100644 apps/sim/tools/box/index.ts create mode 100644 apps/sim/tools/box/list_folder_items.ts create mode 100644 apps/sim/tools/box/search.ts create mode 100644 apps/sim/tools/box/types.ts create mode 100644 apps/sim/tools/box/update_file.ts create mode 100644 apps/sim/tools/box/upload_file.ts create mode 100644 apps/sim/tools/box_sign/cancel_request.ts create mode 100644 apps/sim/tools/box_sign/create_request.ts create mode 100644 apps/sim/tools/box_sign/get_request.ts create mode 100644 apps/sim/tools/box_sign/index.ts create mode 100644 apps/sim/tools/box_sign/list_requests.ts create mode 100644 apps/sim/tools/box_sign/resend_request.ts create mode 100644 apps/sim/tools/box_sign/types.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index bc1b61889a1..32b0bd2a173 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -1409,7 +1409,7 @@ export function AmplitudeIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 986513f4bc8..67b216a99e9 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -16,6 +16,7 @@ import { AsanaIcon, AshbyIcon, AttioIcon, + BoxCompanyIcon, BrainIcon, BrandfetchIcon, BrowserUseIcon, @@ -185,6 +186,8 @@ export const blockTypeToIconMap: Record = { asana: AsanaIcon, ashby: AshbyIcon, attio: AttioIcon, + box: BoxCompanyIcon, + box_sign: BoxCompanyIcon, brandfetch: BrandfetchIcon, browser_use: BrowserUseIcon, calcom: CalComIcon, diff --git a/apps/docs/content/docs/en/tools/box.mdx b/apps/docs/content/docs/en/tools/box.mdx new file mode 100644 index 00000000000..f99ffda4a40 --- /dev/null +++ b/apps/docs/content/docs/en/tools/box.mdx @@ -0,0 +1,280 @@ +--- +title: Box +description: Upload, download, search, and manage files and folders in Box +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Box](https://www.box.com/) is a leading cloud content management and file sharing platform trusted by enterprises worldwide to securely store, manage, and collaborate on files. Box provides robust APIs for automating file operations and integrating with business workflows. + +With the Box integration in Sim, you can: + +- **Upload files**: Upload documents, images, and other files to any Box folder +- **Download files**: Retrieve file content from Box for processing in your workflows +- **Get file info**: Access detailed metadata including size, owner, timestamps, tags, and shared links +- **List folder contents**: Browse files and folders with sorting and pagination support +- **Create folders**: Organize your Box storage by creating new folders programmatically +- **Delete files and folders**: Remove content with optional recursive deletion for folders +- **Copy files**: Duplicate files across folders with optional renaming +- **Search**: Find files and folders by name, content, extension, or location +- **Update file metadata**: Rename, move, add descriptions, or tag files + +These capabilities allow your Sim agents to automate Box operations directly within your workflows — from organizing documents and distributing content to processing uploaded files and maintaining structured cloud storage as part of your business processes. +{/* MANUAL-CONTENT-END */} + +## Usage Instructions + +Integrate Box into your workflow to manage files and folders. Upload and download files, get file information, list folder contents, create and delete folders, copy files, search across your Box account, and update file metadata. + + + +## Tools + +### `box_upload_file` + +Upload a file to a Box folder + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `parentFolderId` | string | Yes | The ID of the folder to upload the file to \(use "0" for root\) | +| `file` | file | No | The file to upload \(UserFile object\) | +| `fileContent` | string | No | Legacy: base64 encoded file content | +| `fileName` | string | No | Optional filename override | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Uploaded file ID | +| `name` | string | Uploaded file name | +| `size` | number | File size in bytes | +| `sha1` | string | SHA1 hash of file content | +| `createdAt` | string | Creation timestamp | +| `modifiedAt` | string | Last modified timestamp | +| `parentId` | string | Parent folder ID | +| `parentName` | string | Parent folder name | + +### `box_download_file` + +Download a file from Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileId` | string | Yes | The ID of the file to download | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `file` | file | Downloaded file stored in execution files | +| `content` | string | Base64 encoded file content | + +### `box_get_file_info` + +Get detailed information about a file in Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileId` | string | Yes | The ID of the file to get information about | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | File ID | +| `name` | string | File name | +| `description` | string | File description | +| `size` | number | File size in bytes | +| `sha1` | string | SHA1 hash of file content | +| `createdAt` | string | Creation timestamp | +| `modifiedAt` | string | Last modified timestamp | +| `createdBy` | object | User who created the file | +| `modifiedBy` | object | User who last modified the file | +| `ownedBy` | object | User who owns the file | +| `parentId` | string | Parent folder ID | +| `parentName` | string | Parent folder name | +| `sharedLink` | json | Shared link details | +| `tags` | array | File tags | +| `commentCount` | number | Number of comments | + +### `box_list_folder_items` + +List files and folders in a Box folder + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | The ID of the folder to list items from \(use "0" for root\) | +| `limit` | number | No | Maximum number of items to return per page | +| `offset` | number | No | The offset for pagination | +| `sort` | string | No | Sort field: id, name, date, or size | +| `direction` | string | No | Sort direction: ASC or DESC | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `totalCount` | number | Total number of items in the folder | +| `offset` | number | Current pagination offset | +| `limit` | number | Current pagination limit | + +### `box_create_folder` + +Create a new folder in Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Name for the new folder | +| `parentFolderId` | string | Yes | The ID of the parent folder \(use "0" for root\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Folder ID | +| `name` | string | Folder name | +| `createdAt` | string | Creation timestamp | +| `modifiedAt` | string | Last modified timestamp | +| `parentId` | string | Parent folder ID | +| `parentName` | string | Parent folder name | + +### `box_delete_file` + +Delete a file from Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileId` | string | Yes | The ID of the file to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the file was successfully deleted | +| `message` | string | Success confirmation message | + +### `box_delete_folder` + +Delete a folder from Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | The ID of the folder to delete | +| `recursive` | boolean | No | Delete folder and all its contents recursively | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the folder was successfully deleted | +| `message` | string | Success confirmation message | + +### `box_copy_file` + +Copy a file to another folder in Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileId` | string | Yes | The ID of the file to copy | +| `parentFolderId` | string | Yes | The ID of the destination folder | +| `name` | string | No | Optional new name for the copied file | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Uploaded file ID | +| `name` | string | Uploaded file name | +| `size` | number | File size in bytes | +| `sha1` | string | SHA1 hash of file content | +| `createdAt` | string | Creation timestamp | +| `modifiedAt` | string | Last modified timestamp | +| `parentId` | string | Parent folder ID | +| `parentName` | string | Parent folder name | + +### `box_search` + +Search for files and folders in Box + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | Yes | The search query string | +| `limit` | number | No | Maximum number of results to return | +| `offset` | number | No | The offset for pagination | +| `ancestorFolderId` | string | No | Restrict search to a specific folder and its subfolders | +| `fileExtensions` | string | No | Comma-separated file extensions to filter by \(e.g., pdf,docx\) | +| `type` | string | No | Restrict to a specific content type: file, folder, or web_link | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | array | Search results | +| ↳ `type` | string | Item type \(file, folder, web_link\) | +| ↳ `id` | string | Item ID | +| ↳ `name` | string | Item name | +| ↳ `size` | number | Item size in bytes | +| ↳ `createdAt` | string | Creation timestamp | +| ↳ `modifiedAt` | string | Last modified timestamp | +| ↳ `parentId` | string | Parent folder ID | +| ↳ `parentName` | string | Parent folder name | +| `totalCount` | number | Total number of matching results | + +### `box_update_file` + +Update file info in Box (rename, move, change description, add tags) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `fileId` | string | Yes | The ID of the file to update | +| `name` | string | No | New name for the file | +| `description` | string | No | New description for the file \(max 256 characters\) | +| `parentFolderId` | string | No | Move the file to a different folder by specifying the folder ID | +| `tags` | string | No | Comma-separated tags to set on the file | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | File ID | +| `name` | string | File name | +| `description` | string | File description | +| `size` | number | File size in bytes | +| `sha1` | string | SHA1 hash of file content | +| `createdAt` | string | Creation timestamp | +| `modifiedAt` | string | Last modified timestamp | +| `createdBy` | object | User who created the file | +| `modifiedBy` | object | User who last modified the file | +| `ownedBy` | object | User who owns the file | +| `parentId` | string | Parent folder ID | +| `parentName` | string | Parent folder name | +| `sharedLink` | json | Shared link details | +| `tags` | array | File tags | +| `commentCount` | number | Number of comments | + + diff --git a/apps/docs/content/docs/en/tools/box_sign.mdx b/apps/docs/content/docs/en/tools/box_sign.mdx new file mode 100644 index 00000000000..895c38b4ca0 --- /dev/null +++ b/apps/docs/content/docs/en/tools/box_sign.mdx @@ -0,0 +1,183 @@ +--- +title: Box Sign +description: Send documents for e-signature, check status, and manage sign requests with Box Sign +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Box Sign](https://www.box.com/esignature) is Box's native e-signature solution that allows you to send documents for legally binding electronic signatures directly from your Box account. It's built into Box, so signed documents are automatically stored and organized in your cloud content. + +With the Box Sign integration in Sim, you can: + +- **Create sign requests**: Send documents for e-signature with one or more signers, specifying roles (signer, approver, final copy reader) +- **Track signing status**: Monitor the progress of sign requests including who has signed and pending actions +- **List sign requests**: View all sign requests with marker-based pagination +- **Cancel requests**: Cancel pending sign requests that are no longer needed +- **Resend reminders**: Send reminder notifications to signers who haven't completed signing + +The Box Sign integration is ideal for automating document signing workflows such as offer letters, contracts, NDAs, and approval processes. Agents can create sign requests from documents already stored in Box, track completion, and trigger follow-up actions based on signing status. +{/* MANUAL-CONTENT-END */} + +## Usage Instructions + +Integrate Box Sign into your workflow to send documents for e-signature. Create sign requests with multiple signers, track signing status, list all requests, cancel pending requests, and resend reminders. Ideal for offer letters, contracts, and other documents requiring signatures. + + + +## Tools + +### `box_sign_create_request` + +Create a new Box Sign request to send documents for e-signature + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `sourceFileIds` | string | Yes | Comma-separated Box file IDs to send for signing | +| `signerEmail` | string | Yes | Primary signer email address | +| `signerRole` | string | No | Primary signer role: signer, approver, or final_copy_reader \(default: signer\) | +| `additionalSigners` | string | No | JSON array of additional signers, e.g. \[\{"email":"user@example.com","role":"signer"\}\] | +| `parentFolderId` | string | No | Box folder ID where signed documents will be stored \(default: user root\) | +| `emailSubject` | string | No | Custom subject line for the signing email | +| `emailMessage` | string | No | Custom message in the signing email body | +| `name` | string | No | Name for the sign request | +| `daysValid` | number | No | Number of days before the request expires \(0-730\) | +| `areRemindersEnabled` | boolean | No | Whether to send automatic signing reminders | +| `areTextSignaturesEnabled` | boolean | No | Whether to allow typed \(text\) signatures | +| `signatureColor` | string | No | Signature color: blue, black, or red | +| `redirectUrl` | string | No | URL to redirect signers to after signing | +| `declinedRedirectUrl` | string | No | URL to redirect signers to after declining | +| `isDocumentPreparationNeeded` | boolean | No | Whether document preparation is needed before sending | +| `externalId` | string | No | External system reference ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Sign request ID | +| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| `name` | string | Sign request name | +| `shortId` | string | Human-readable short ID | +| `signers` | array | List of signers | +| `sourceFiles` | array | Source files for signing | +| `emailSubject` | string | Custom email subject line | +| `emailMessage` | string | Custom email message body | +| `daysValid` | number | Number of days the request is valid | +| `createdAt` | string | Creation timestamp | +| `autoExpireAt` | string | Auto-expiration timestamp | +| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| `senderEmail` | string | Email of the sender | + +### `box_sign_get_request` + +Get the details and status of a Box Sign request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `signRequestId` | string | Yes | The ID of the sign request to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Sign request ID | +| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| `name` | string | Sign request name | +| `shortId` | string | Human-readable short ID | +| `signers` | array | List of signers | +| `sourceFiles` | array | Source files for signing | +| `emailSubject` | string | Custom email subject line | +| `emailMessage` | string | Custom email message body | +| `daysValid` | number | Number of days the request is valid | +| `createdAt` | string | Creation timestamp | +| `autoExpireAt` | string | Auto-expiration timestamp | +| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| `senderEmail` | string | Email of the sender | + +### `box_sign_list_requests` + +List all Box Sign requests + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `limit` | number | No | Maximum number of sign requests to return \(max 1000\) | +| `marker` | string | No | Pagination marker from a previous response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `signRequests` | array | List of sign requests | +| ↳ `id` | string | Sign request ID | +| ↳ `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| ↳ `name` | string | Sign request name | +| ↳ `shortId` | string | Human-readable short ID | +| ↳ `signers` | array | List of signers | +| ↳ `sourceFiles` | array | Source files for signing | +| ↳ `emailSubject` | string | Custom email subject line | +| ↳ `emailMessage` | string | Custom email message body | +| ↳ `daysValid` | number | Number of days the request is valid | +| ↳ `createdAt` | string | Creation timestamp | +| ↳ `autoExpireAt` | string | Auto-expiration timestamp | +| ↳ `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| ↳ `senderEmail` | string | Email of the sender | +| `count` | number | Number of sign requests returned in this page | +| `nextMarker` | string | Marker for next page of results | + +### `box_sign_cancel_request` + +Cancel a pending Box Sign request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `signRequestId` | string | Yes | The ID of the sign request to cancel | +| `reason` | string | No | Optional reason for cancellation | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Sign request ID | +| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| `name` | string | Sign request name | +| `shortId` | string | Human-readable short ID | +| `signers` | array | List of signers | +| `sourceFiles` | array | Source files for signing | +| `emailSubject` | string | Custom email subject line | +| `emailMessage` | string | Custom email message body | +| `daysValid` | number | Number of days the request is valid | +| `createdAt` | string | Creation timestamp | +| `autoExpireAt` | string | Auto-expiration timestamp | +| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| `senderEmail` | string | Email of the sender | + +### `box_sign_resend_request` + +Resend a Box Sign request to signers who have not yet signed + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `signRequestId` | string | Yes | The ID of the sign request to resend | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Success confirmation message | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index ccbca5986af..70548f0b052 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -13,6 +13,8 @@ "asana", "ashby", "attio", + "box", + "box_sign", "brandfetch", "browser_use", "calcom", diff --git a/apps/sim/app/api/tools/box/upload/route.ts b/apps/sim/app/api/tools/box/upload/route.ts new file mode 100644 index 00000000000..f99ade2e844 --- /dev/null +++ b/apps/sim/app/api/tools/box/upload/route.ts @@ -0,0 +1,140 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { FileInputSchema } from '@/lib/uploads/utils/file-schemas' +import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('BoxUploadAPI') + +const BoxUploadSchema = z.object({ + accessToken: z.string().min(1, 'Access token is required'), + parentFolderId: z.string().min(1, 'Parent folder ID is required'), + file: FileInputSchema.optional().nullable(), + fileContent: z.string().optional().nullable(), + fileName: z.string().optional().nullable(), +}) + +export async function POST(request: NextRequest) { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Box upload attempt: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + logger.info(`[${requestId}] Authenticated Box upload request via ${authResult.authType}`) + + const body = await request.json() + const validatedData = BoxUploadSchema.parse(body) + + let fileBuffer: Buffer + let fileName: string + + if (validatedData.file) { + const userFiles = processFilesToUserFiles( + [validatedData.file as RawFileInput], + requestId, + logger + ) + + if (userFiles.length === 0) { + return NextResponse.json({ success: false, error: 'Invalid file input' }, { status: 400 }) + } + + const userFile = userFiles[0] + logger.info(`[${requestId}] Downloading file: ${userFile.name} (${userFile.size} bytes)`) + + fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) + fileName = validatedData.fileName || userFile.name + } else if (validatedData.fileContent) { + logger.info(`[${requestId}] Using legacy base64 content input`) + fileBuffer = Buffer.from(validatedData.fileContent, 'base64') + fileName = validatedData.fileName || 'file' + } else { + return NextResponse.json({ success: false, error: 'File is required' }, { status: 400 }) + } + + logger.info( + `[${requestId}] Uploading to Box folder ${validatedData.parentFolderId}: ${fileName} (${fileBuffer.length} bytes)` + ) + + const attributes = JSON.stringify({ + name: fileName, + parent: { id: validatedData.parentFolderId }, + }) + + const formData = new FormData() + formData.append('attributes', attributes) + formData.append( + 'file', + new Blob([new Uint8Array(fileBuffer)], { type: 'application/octet-stream' }), + fileName + ) + + const response = await fetch('https://upload.box.com/api/2.0/files/content', { + method: 'POST', + headers: { + Authorization: `Bearer ${validatedData.accessToken}`, + }, + body: formData, + }) + + const data = await response.json() + + if (!response.ok) { + const errorMessage = data.message || 'Failed to upload file' + logger.error(`[${requestId}] Box API error:`, { status: response.status, data }) + return NextResponse.json({ success: false, error: errorMessage }, { status: response.status }) + } + + const file = data.entries?.[0] + + if (!file) { + return NextResponse.json( + { success: false, error: 'No file returned in upload response' }, + { status: 500 } + ) + } + + logger.info(`[${requestId}] File uploaded successfully: ${file.name} (ID: ${file.id})`) + + return NextResponse.json({ + success: true, + output: { + id: file.id ?? '', + name: file.name ?? '', + size: file.size ?? 0, + sha1: file.sha1 ?? null, + createdAt: file.created_at ?? null, + modifiedAt: file.modified_at ?? null, + parentId: file.parent?.id ?? null, + parentName: file.parent?.name ?? null, + }, + }) + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Validation error:`, error.errors) + return NextResponse.json( + { success: false, error: error.errors[0]?.message || 'Validation failed' }, + { status: 400 } + ) + } + + logger.error(`[${requestId}] Unexpected error:`, error) + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ) + } +} diff --git a/apps/sim/blocks/blocks/box.ts b/apps/sim/blocks/blocks/box.ts new file mode 100644 index 00000000000..57a2b193907 --- /dev/null +++ b/apps/sim/blocks/blocks/box.ts @@ -0,0 +1,379 @@ +import { BoxCompanyIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' + +export const BoxBlock: BlockConfig = { + type: 'box', + name: 'Box', + description: 'Upload, download, search, and manage files and folders in Box', + longDescription: + 'Integrate Box into your workflow to manage files and folders. Upload and download files, get file information, list folder contents, create and delete folders, copy files, search across your Box account, and update file metadata.', + docsLink: 'https://docs.sim.ai/tools/box', + category: 'tools', + bgColor: '#0061D5', + icon: BoxCompanyIcon, + authMode: AuthMode.OAuth, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Upload File', id: 'upload_file' }, + { label: 'Download File', id: 'download_file' }, + { label: 'Get File Info', id: 'get_file_info' }, + { label: 'List Folder Items', id: 'list_folder_items' }, + { label: 'Create Folder', id: 'create_folder' }, + { label: 'Delete File', id: 'delete_file' }, + { label: 'Delete Folder', id: 'delete_folder' }, + { label: 'Copy File', id: 'copy_file' }, + { label: 'Search', id: 'search' }, + { label: 'Update File', id: 'update_file' }, + ], + value: () => 'upload_file', + }, + { + id: 'credential', + title: 'Box Account', + type: 'oauth-input', + serviceId: 'box', + requiredScopes: getScopesForService('box'), + placeholder: 'Select Box account', + required: true, + }, + + // Upload File fields + { + id: 'uploadFile', + title: 'File', + type: 'file-upload', + canonicalParamId: 'file', + placeholder: 'Upload file to send to Box', + mode: 'basic', + multiple: false, + required: { field: 'operation', value: 'upload_file' }, + condition: { field: 'operation', value: 'upload_file' }, + }, + { + id: 'fileRef', + title: 'File', + type: 'short-input', + canonicalParamId: 'file', + placeholder: 'Reference file from previous blocks', + mode: 'advanced', + required: { field: 'operation', value: 'upload_file' }, + condition: { field: 'operation', value: 'upload_file' }, + }, + { + id: 'parentFolderId', + title: 'Parent Folder ID', + type: 'short-input', + placeholder: 'Folder ID (use "0" for root)', + required: { field: 'operation', value: ['upload_file', 'create_folder', 'copy_file'] }, + condition: { field: 'operation', value: ['upload_file', 'create_folder', 'copy_file'] }, + }, + { + id: 'uploadFileName', + title: 'File Name', + type: 'short-input', + placeholder: 'Optional filename override', + condition: { field: 'operation', value: 'upload_file' }, + mode: 'advanced', + }, + + // File ID field (shared by download, get info, delete, copy, update) + { + id: 'fileId', + title: 'File ID', + type: 'short-input', + placeholder: 'Box file ID', + required: { + field: 'operation', + value: ['download_file', 'get_file_info', 'delete_file', 'copy_file', 'update_file'], + }, + condition: { + field: 'operation', + value: ['download_file', 'get_file_info', 'delete_file', 'copy_file', 'update_file'], + }, + }, + + // Folder ID field (shared by list, delete folder) + { + id: 'folderId', + title: 'Folder ID', + type: 'short-input', + placeholder: 'Box folder ID (use "0" for root)', + required: { field: 'operation', value: ['list_folder_items', 'delete_folder'] }, + condition: { field: 'operation', value: ['list_folder_items', 'delete_folder'] }, + }, + + // Create Folder fields + { + id: 'folderName', + title: 'Folder Name', + type: 'short-input', + placeholder: 'Name for the new folder', + required: { field: 'operation', value: 'create_folder' }, + condition: { field: 'operation', value: 'create_folder' }, + }, + + // Copy File fields + { + id: 'copyName', + title: 'New Name', + type: 'short-input', + placeholder: 'Optional name for the copy', + condition: { field: 'operation', value: 'copy_file' }, + }, + + // Search fields + { + id: 'query', + title: 'Search Query', + type: 'short-input', + placeholder: 'Search query string', + required: { field: 'operation', value: 'search' }, + condition: { field: 'operation', value: 'search' }, + }, + { + id: 'ancestorFolderId', + title: 'Ancestor Folder ID', + type: 'short-input', + placeholder: 'Restrict search to a folder', + condition: { field: 'operation', value: 'search' }, + mode: 'advanced', + }, + { + id: 'fileExtensions', + title: 'File Extensions', + type: 'short-input', + placeholder: 'e.g., pdf,docx,xlsx', + condition: { field: 'operation', value: 'search' }, + mode: 'advanced', + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Files', id: 'file' }, + { label: 'Folders', id: 'folder' }, + { label: 'Web Links', id: 'web_link' }, + ], + value: () => '', + condition: { field: 'operation', value: 'search' }, + mode: 'advanced', + }, + + // Update File fields + { + id: 'newName', + title: 'New Name', + type: 'short-input', + placeholder: 'Rename the file', + condition: { field: 'operation', value: 'update_file' }, + }, + { + id: 'description', + title: 'Description', + type: 'short-input', + placeholder: 'File description (max 256 chars)', + condition: { field: 'operation', value: 'update_file' }, + }, + { + id: 'moveToFolderId', + title: 'Move to Folder ID', + type: 'short-input', + placeholder: 'Move file to this folder', + condition: { field: 'operation', value: 'update_file' }, + mode: 'advanced', + }, + { + id: 'tags', + title: 'Tags', + type: 'short-input', + placeholder: 'Comma-separated tags', + condition: { field: 'operation', value: 'update_file' }, + mode: 'advanced', + }, + + // Delete Folder options + { + id: 'recursive', + title: 'Delete Recursively', + type: 'switch', + condition: { field: 'operation', value: 'delete_folder' }, + }, + + // Shared pagination fields + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Max results per page', + condition: { field: 'operation', value: ['list_folder_items', 'search'] }, + mode: 'advanced', + }, + { + id: 'offset', + title: 'Offset', + type: 'short-input', + placeholder: 'Pagination offset', + condition: { field: 'operation', value: ['list_folder_items', 'search'] }, + mode: 'advanced', + }, + + // List Folder sort options + { + id: 'sort', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'ID', id: 'id' }, + { label: 'Name', id: 'name' }, + { label: 'Date', id: 'date' }, + { label: 'Size', id: 'size' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_folder_items' }, + mode: 'advanced', + }, + { + id: 'direction', + title: 'Sort Direction', + type: 'dropdown', + options: [ + { label: 'Ascending', id: 'ASC' }, + { label: 'Descending', id: 'DESC' }, + ], + value: () => 'ASC', + condition: { field: 'operation', value: 'list_folder_items' }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'box_upload_file', + 'box_download_file', + 'box_get_file_info', + 'box_list_folder_items', + 'box_create_folder', + 'box_delete_file', + 'box_delete_folder', + 'box_copy_file', + 'box_search', + 'box_update_file', + ], + config: { + tool: (params) => { + const normalizedFile = normalizeFileInput(params.uploadFile || params.fileRef, { + single: true, + }) + if (normalizedFile) { + params.file = normalizedFile + } + return `box_${params.operation}` + }, + params: (params) => { + const { credential, operation, ...rest } = params + + const baseParams: Record = { + accessToken: credential, + } + + switch (operation) { + case 'upload_file': + baseParams.parentFolderId = rest.parentFolderId + baseParams.file = rest.file + if (rest.uploadFileName) baseParams.fileName = rest.uploadFileName + break + case 'download_file': + case 'get_file_info': + case 'delete_file': + baseParams.fileId = rest.fileId + break + case 'list_folder_items': + baseParams.folderId = rest.folderId + if (rest.limit) baseParams.limit = Number(rest.limit) + if (rest.offset) baseParams.offset = Number(rest.offset) + if (rest.sort) baseParams.sort = rest.sort + if (rest.direction) baseParams.direction = rest.direction + break + case 'create_folder': + baseParams.name = rest.folderName + baseParams.parentFolderId = rest.parentFolderId + break + case 'delete_folder': + baseParams.folderId = rest.folderId + if (rest.recursive !== undefined) baseParams.recursive = rest.recursive + break + case 'copy_file': + baseParams.fileId = rest.fileId + baseParams.parentFolderId = rest.parentFolderId + if (rest.copyName) baseParams.name = rest.copyName + break + case 'search': + baseParams.query = rest.query + if (rest.limit) baseParams.limit = Number(rest.limit) + if (rest.offset) baseParams.offset = Number(rest.offset) + if (rest.ancestorFolderId) baseParams.ancestorFolderId = rest.ancestorFolderId + if (rest.fileExtensions) baseParams.fileExtensions = rest.fileExtensions + if (rest.contentType) baseParams.type = rest.contentType + break + case 'update_file': + baseParams.fileId = rest.fileId + if (rest.newName) baseParams.name = rest.newName + if (rest.description !== undefined) baseParams.description = rest.description + if (rest.moveToFolderId) baseParams.parentFolderId = rest.moveToFolderId + if (rest.tags) baseParams.tags = rest.tags + break + } + + return baseParams + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + credential: { type: 'string', description: 'Box OAuth credential' }, + file: { type: 'json', description: 'File to upload (canonical param)' }, + fileId: { type: 'string', description: 'Box file ID' }, + folderId: { type: 'string', description: 'Box folder ID' }, + parentFolderId: { type: 'string', description: 'Parent folder ID' }, + query: { type: 'string', description: 'Search query' }, + }, + + outputs: { + id: 'string', + name: 'string', + description: 'string', + size: 'number', + sha1: 'string', + createdAt: 'string', + modifiedAt: 'string', + createdBy: 'json', + modifiedBy: 'json', + ownedBy: 'json', + parentId: 'string', + parentName: 'string', + sharedLink: 'json', + tags: 'json', + commentCount: 'number', + file: 'file', + content: 'string', + items: 'json', + totalCount: 'number', + offset: 'number', + limit: 'number', + results: 'json', + deleted: 'boolean', + message: 'string', + }, +} diff --git a/apps/sim/blocks/blocks/box_sign.ts b/apps/sim/blocks/blocks/box_sign.ts new file mode 100644 index 00000000000..9b29ab4161a --- /dev/null +++ b/apps/sim/blocks/blocks/box_sign.ts @@ -0,0 +1,302 @@ +import { BoxCompanyIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' + +export const BoxSignBlock: BlockConfig = { + type: 'box_sign', + name: 'Box Sign', + description: + 'Send documents for e-signature, check status, and manage sign requests with Box Sign', + longDescription: + 'Integrate Box Sign into your workflow to send documents for e-signature. Create sign requests with multiple signers, track signing status, list all requests, cancel pending requests, and resend reminders. Ideal for offer letters, contracts, and other documents requiring signatures.', + docsLink: 'https://docs.sim.ai/tools/box-sign', + category: 'tools', + bgColor: '#0061D5', + icon: BoxCompanyIcon, + authMode: AuthMode.OAuth, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Sign Request', id: 'create_request' }, + { label: 'Get Sign Request', id: 'get_request' }, + { label: 'List Sign Requests', id: 'list_requests' }, + { label: 'Cancel Sign Request', id: 'cancel_request' }, + { label: 'Resend Sign Request', id: 'resend_request' }, + ], + value: () => 'create_request', + }, + { + id: 'credential', + title: 'Box Account', + type: 'oauth-input', + serviceId: 'box', + requiredScopes: getScopesForService('box'), + placeholder: 'Select Box account', + required: true, + }, + + // Create Sign Request fields + { + id: 'sourceFileIds', + title: 'Source File IDs', + type: 'short-input', + placeholder: 'Comma-separated Box file IDs (e.g., 12345,67890)', + required: { field: 'operation', value: 'create_request' }, + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'signerEmail', + title: 'Signer Email', + type: 'short-input', + placeholder: 'Primary signer email address', + required: { field: 'operation', value: 'create_request' }, + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'signerRole', + title: 'Signer Role', + type: 'dropdown', + options: [ + { label: 'Signer', id: 'signer' }, + { label: 'Approver', id: 'approver' }, + { label: 'Final Copy Reader', id: 'final_copy_reader' }, + ], + value: () => 'signer', + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'emailSubject', + title: 'Email Subject', + type: 'short-input', + placeholder: 'Custom email subject line', + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'emailMessage', + title: 'Email Message', + type: 'long-input', + placeholder: 'Custom message in the signing email', + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'name', + title: 'Request Name', + type: 'short-input', + placeholder: 'Name for this sign request', + condition: { field: 'operation', value: 'create_request' }, + }, + { + id: 'additionalSigners', + title: 'Additional Signers', + type: 'long-input', + placeholder: '[{"email":"user@example.com","role":"signer"}]', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'parentFolderId', + title: 'Destination Folder ID', + type: 'short-input', + placeholder: 'Box folder ID for signed documents', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'daysValid', + title: 'Days Valid', + type: 'short-input', + placeholder: 'Number of days before expiry (0-730)', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'areRemindersEnabled', + title: 'Enable Reminders', + type: 'switch', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'areTextSignaturesEnabled', + title: 'Allow Text Signatures', + type: 'switch', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'signatureColor', + title: 'Signature Color', + type: 'dropdown', + options: [ + { label: 'Blue', id: 'blue' }, + { label: 'Black', id: 'black' }, + { label: 'Red', id: 'red' }, + ], + value: () => 'blue', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'redirectUrl', + title: 'Redirect URL', + type: 'short-input', + placeholder: 'URL to redirect after signing', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'declinedRedirectUrl', + title: 'Declined Redirect URL', + type: 'short-input', + placeholder: 'URL to redirect after declining', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'isDocumentPreparationNeeded', + title: 'Document Preparation Needed', + type: 'switch', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + { + id: 'externalId', + title: 'External ID', + type: 'short-input', + placeholder: 'External system reference ID', + condition: { field: 'operation', value: 'create_request' }, + mode: 'advanced', + }, + + // Get / Cancel / Resend fields + { + id: 'signRequestId', + title: 'Sign Request ID', + type: 'short-input', + placeholder: 'Box Sign request ID', + required: { field: 'operation', value: ['get_request', 'cancel_request', 'resend_request'] }, + condition: { + field: 'operation', + value: ['get_request', 'cancel_request', 'resend_request'], + }, + }, + + // Cancel-specific fields + { + id: 'reason', + title: 'Cancellation Reason', + type: 'short-input', + placeholder: 'Optional reason for cancellation', + condition: { field: 'operation', value: 'cancel_request' }, + }, + + // List fields + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Max results (default: 100, max: 1000)', + condition: { field: 'operation', value: 'list_requests' }, + mode: 'advanced', + }, + { + id: 'marker', + title: 'Pagination Marker', + type: 'short-input', + placeholder: 'Marker from previous response', + condition: { field: 'operation', value: 'list_requests' }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'box_sign_create_request', + 'box_sign_get_request', + 'box_sign_list_requests', + 'box_sign_cancel_request', + 'box_sign_resend_request', + ], + config: { + tool: (params) => `box_sign_${params.operation}`, + params: (params) => { + const { credential, operation, ...rest } = params + + const baseParams: Record = { + accessToken: credential, + } + + switch (operation) { + case 'create_request': + baseParams.sourceFileIds = rest.sourceFileIds + baseParams.signerEmail = rest.signerEmail + if (rest.signerRole) baseParams.signerRole = rest.signerRole + if (rest.additionalSigners) baseParams.additionalSigners = rest.additionalSigners + if (rest.parentFolderId) baseParams.parentFolderId = rest.parentFolderId + if (rest.emailSubject) baseParams.emailSubject = rest.emailSubject + if (rest.emailMessage) baseParams.emailMessage = rest.emailMessage + if (rest.name) baseParams.name = rest.name + if (rest.daysValid) baseParams.daysValid = Number(rest.daysValid) + if (rest.areRemindersEnabled !== undefined) + baseParams.areRemindersEnabled = rest.areRemindersEnabled + if (rest.areTextSignaturesEnabled !== undefined) + baseParams.areTextSignaturesEnabled = rest.areTextSignaturesEnabled + if (rest.signatureColor) baseParams.signatureColor = rest.signatureColor + if (rest.redirectUrl) baseParams.redirectUrl = rest.redirectUrl + if (rest.declinedRedirectUrl) baseParams.declinedRedirectUrl = rest.declinedRedirectUrl + if (rest.isDocumentPreparationNeeded !== undefined) + baseParams.isDocumentPreparationNeeded = rest.isDocumentPreparationNeeded + if (rest.externalId) baseParams.externalId = rest.externalId + break + case 'get_request': + case 'resend_request': + baseParams.signRequestId = rest.signRequestId + break + case 'cancel_request': + baseParams.signRequestId = rest.signRequestId + if (rest.reason) baseParams.reason = rest.reason + break + case 'list_requests': + if (rest.limit) baseParams.limit = Number(rest.limit) + if (rest.marker) baseParams.marker = rest.marker + break + } + + return baseParams + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + credential: { type: 'string', description: 'Box OAuth credential' }, + sourceFileIds: { type: 'string', description: 'Comma-separated Box file IDs' }, + signerEmail: { type: 'string', description: 'Primary signer email address' }, + signRequestId: { type: 'string', description: 'Sign request ID' }, + }, + + outputs: { + id: 'string', + status: 'string', + name: 'string', + shortId: 'string', + signers: 'json', + sourceFiles: 'json', + emailSubject: 'string', + emailMessage: 'string', + daysValid: 'number', + createdAt: 'string', + autoExpireAt: 'string', + prepareUrl: 'string', + senderEmail: 'string', + signRequests: 'json', + count: 'number', + nextMarker: 'string', + message: 'string', + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 51c2c2e2252..2e0802cc51d 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -13,6 +13,8 @@ import { ArxivBlock } from '@/blocks/blocks/arxiv' import { AsanaBlock } from '@/blocks/blocks/asana' import { AshbyBlock } from '@/blocks/blocks/ashby' import { AttioBlock } from '@/blocks/blocks/attio' +import { BoxBlock } from '@/blocks/blocks/box' +import { BoxSignBlock } from '@/blocks/blocks/box_sign' import { BrandfetchBlock } from '@/blocks/blocks/brandfetch' import { BrowserUseBlock } from '@/blocks/blocks/browser_use' import { CalComBlock } from '@/blocks/blocks/calcom' @@ -216,6 +218,8 @@ export const registry: Record = { ashby: AshbyBlock, attio: AttioBlock, brandfetch: BrandfetchBlock, + box: BoxBlock, + box_sign: BoxSignBlock, browser_use: BrowserUseBlock, calcom: CalComBlock, calendly: CalendlyBlock, diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index ee4a22f4fe8..29d46e99db3 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -478,6 +478,7 @@ export const auth = betterAuth({ 'sharepoint', 'jira', 'airtable', + 'box', 'dropbox', 'salesforce', 'wealthbox', @@ -2177,6 +2178,51 @@ export const auth = betterAuth({ }, }, + { + providerId: 'box', + clientId: env.BOX_CLIENT_ID as string, + clientSecret: env.BOX_CLIENT_SECRET as string, + authorizationUrl: 'https://account.box.com/api/oauth2/authorize', + tokenUrl: 'https://api.box.com/oauth2/token', + scopes: getCanonicalScopesForProvider('box'), + responseType: 'code', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/box`, + getUserInfo: async (tokens) => { + try { + const response = await fetch('https://api.box.com/2.0/users/me', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + + if (!response.ok) { + const errorText = await response.text() + logger.error('Box API error:', { + status: response.status, + statusText: response.statusText, + body: errorText, + }) + throw new Error(`Box API error: ${response.status} ${response.statusText}`) + } + + const data = await response.json() + + return { + id: `${data.id}-${crypto.randomUUID()}`, + email: data.login, + name: data.name || data.login, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + image: data.avatar_url || undefined, + } + } catch (error) { + logger.error('Error in Box getUserInfo:', error) + throw error + } + }, + }, + { providerId: 'dropbox', clientId: env.DROPBOX_CLIENT_ID as string, diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index cd46579ac45..77408fb7e3b 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -277,6 +277,8 @@ export const env = createEnv({ PIPEDRIVE_CLIENT_SECRET: z.string().optional(), // Pipedrive OAuth client secret LINEAR_CLIENT_ID: z.string().optional(), // Linear OAuth client ID LINEAR_CLIENT_SECRET: z.string().optional(), // Linear OAuth client secret + BOX_CLIENT_ID: z.string().optional(), // Box OAuth client ID + BOX_CLIENT_SECRET: z.string().optional(), // Box OAuth client secret DROPBOX_CLIENT_ID: z.string().optional(), // Dropbox OAuth client ID DROPBOX_CLIENT_SECRET: z.string().optional(), // Dropbox OAuth client secret SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 47f46817336..62a3fa95b49 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -3,6 +3,7 @@ import { AirtableIcon, AsanaIcon, AttioIcon, + BoxCompanyIcon, CalComIcon, ConfluenceIcon, DocuSignIcon, @@ -572,6 +573,21 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'linear', }, + box: { + name: 'Box', + icon: BoxCompanyIcon, + services: { + box: { + name: 'Box', + description: 'Manage files, folders, and e-signatures with Box.', + providerId: 'box', + icon: BoxCompanyIcon, + baseProviderIcon: BoxCompanyIcon, + scopes: ['root_readwrite'], + }, + }, + defaultService: 'box', + }, dropbox: { name: 'Dropbox', icon: DropboxIcon, @@ -1125,6 +1141,15 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { useBasicAuth: false, } } + case 'box': { + const { clientId, clientSecret } = getCredentials(env.BOX_CLIENT_ID, env.BOX_CLIENT_SECRET) + return { + tokenEndpoint: 'https://api.box.com/oauth2/token', + clientId, + clientSecret, + useBasicAuth: false, + } + } case 'docusign': { const { clientId, clientSecret } = getCredentials( env.DOCUSIGN_CLIENT_ID, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 0db04e5e567..f49e87c8e03 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -21,6 +21,7 @@ export type OAuthProvider = | 'airtable' | 'notion' | 'jira' + | 'box' | 'dropbox' | 'microsoft' | 'microsoft-dataverse' @@ -70,6 +71,7 @@ export type OAuthService = | 'airtable' | 'notion' | 'jira' + | 'box' | 'dropbox' | 'microsoft-dataverse' | 'microsoft-excel' diff --git a/apps/sim/tools/box/copy_file.ts b/apps/sim/tools/box/copy_file.ts new file mode 100644 index 00000000000..d83797920a9 --- /dev/null +++ b/apps/sim/tools/box/copy_file.ts @@ -0,0 +1,82 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxCopyFileParams, BoxUploadFileResponse } from './types' +import { UPLOAD_FILE_OUTPUT_PROPERTIES } from './types' + +export const boxCopyFileTool: ToolConfig = { + id: 'box_copy_file', + name: 'Box Copy File', + description: 'Copy a file to another folder in Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + fileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the file to copy', + }, + parentFolderId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the destination folder', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional new name for the copied file', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/files/${params.fileId.trim()}/copy`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + parent: { id: params.parentFolderId.trim() }, + } + if (params.name) body.name = params.name + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + size: data.size ?? 0, + sha1: data.sha1 ?? null, + createdAt: data.created_at ?? null, + modifiedAt: data.modified_at ?? null, + parentId: data.parent?.id ?? null, + parentName: data.parent?.name ?? null, + }, + } + }, + + outputs: UPLOAD_FILE_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box/create_folder.ts b/apps/sim/tools/box/create_folder.ts new file mode 100644 index 00000000000..c5df14f96bc --- /dev/null +++ b/apps/sim/tools/box/create_folder.ts @@ -0,0 +1,71 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxCreateFolderParams, BoxFolderResponse } from './types' +import { FOLDER_OUTPUT_PROPERTIES } from './types' + +export const boxCreateFolderTool: ToolConfig = { + id: 'box_create_folder', + name: 'Box Create Folder', + description: 'Create a new folder in Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name for the new folder', + }, + parentFolderId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the parent folder (use "0" for root)', + }, + }, + + request: { + url: 'https://api.box.com/2.0/folders', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => ({ + name: params.name, + parent: { id: params.parentFolderId.trim() }, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + createdAt: data.created_at ?? null, + modifiedAt: data.modified_at ?? null, + parentId: data.parent?.id ?? null, + parentName: data.parent?.name ?? null, + }, + } + }, + + outputs: FOLDER_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box/delete_file.ts b/apps/sim/tools/box/delete_file.ts new file mode 100644 index 00000000000..74d429e4afa --- /dev/null +++ b/apps/sim/tools/box/delete_file.ts @@ -0,0 +1,57 @@ +import type { ToolConfig, ToolResponse } from '@/tools/types' +import type { BoxDeleteFileParams } from './types' + +export const boxDeleteFileTool: ToolConfig = { + id: 'box_delete_file', + name: 'Box Delete File', + description: 'Delete a file from Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + fileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the file to delete', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/files/${params.fileId.trim()}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + if (response.status === 204) { + return { + success: true, + output: { + deleted: true, + message: 'File deleted successfully', + }, + } + } + + const data = await response.json() + throw new Error(data.message || `Box API error: ${response.status}`) + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the file was successfully deleted' }, + message: { type: 'string', description: 'Success confirmation message' }, + }, +} diff --git a/apps/sim/tools/box/delete_folder.ts b/apps/sim/tools/box/delete_folder.ts new file mode 100644 index 00000000000..b5fb2206b49 --- /dev/null +++ b/apps/sim/tools/box/delete_folder.ts @@ -0,0 +1,68 @@ +import type { ToolConfig, ToolResponse } from '@/tools/types' +import type { BoxDeleteFolderParams } from './types' + +export const boxDeleteFolderTool: ToolConfig = { + id: 'box_delete_folder', + name: 'Box Delete Folder', + description: 'Delete a folder from Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + folderId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the folder to delete', + }, + recursive: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Delete folder and all its contents recursively', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.recursive) queryParams.set('recursive', 'true') + const qs = queryParams.toString() + return `https://api.box.com/2.0/folders/${params.folderId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + if (response.status === 204) { + return { + success: true, + output: { + deleted: true, + message: 'Folder deleted successfully', + }, + } + } + + const data = await response.json() + throw new Error(data.message || `Box API error: ${response.status}`) + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the folder was successfully deleted' }, + message: { type: 'string', description: 'Success confirmation message' }, + }, +} diff --git a/apps/sim/tools/box/download_file.ts b/apps/sim/tools/box/download_file.ts new file mode 100644 index 00000000000..24366105742 --- /dev/null +++ b/apps/sim/tools/box/download_file.ts @@ -0,0 +1,87 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxDownloadFileParams, BoxDownloadFileResponse } from './types' + +export const boxDownloadFileTool: ToolConfig = { + id: 'box_download_file', + name: 'Box Download File', + description: 'Download a file from Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + fileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the file to download', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/files/${params.fileId.trim()}/content`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + if (response.status === 202) { + const retryAfter = response.headers.get('retry-after') || 'a few' + throw new Error(`File is not yet ready for download. Retry after ${retryAfter} seconds.`) + } + + if (!response.ok) { + const errorText = await response.text() + throw new Error(errorText || `Failed to download file: ${response.status}`) + } + + const contentType = response.headers.get('content-type') || 'application/octet-stream' + const contentDisposition = response.headers.get('content-disposition') + let fileName = 'download' + + if (contentDisposition) { + const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/) + if (match?.[1]) { + fileName = match[1].replace(/['"]/g, '') + } + } + + const arrayBuffer = await response.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + + return { + success: true, + output: { + file: { + name: fileName, + mimeType: contentType, + data: buffer.toString('base64'), + size: buffer.length, + }, + content: buffer.toString('base64'), + }, + } + }, + + outputs: { + file: { + type: 'file', + description: 'Downloaded file stored in execution files', + }, + content: { + type: 'string', + description: 'Base64 encoded file content', + }, + }, +} diff --git a/apps/sim/tools/box/get_file_info.ts b/apps/sim/tools/box/get_file_info.ts new file mode 100644 index 00000000000..9fb978a9a00 --- /dev/null +++ b/apps/sim/tools/box/get_file_info.ts @@ -0,0 +1,87 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxFileInfoResponse, BoxGetFileInfoParams } from './types' +import { FILE_OUTPUT_PROPERTIES } from './types' + +export const boxGetFileInfoTool: ToolConfig = { + id: 'box_get_file_info', + name: 'Box Get File Info', + description: 'Get detailed information about a file in Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + fileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the file to get information about', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/files/${params.fileId.trim()}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + description: data.description ?? null, + size: data.size ?? 0, + sha1: data.sha1 ?? null, + createdAt: data.created_at ?? null, + modifiedAt: data.modified_at ?? null, + createdBy: data.created_by + ? { + id: data.created_by.id, + name: data.created_by.name, + login: data.created_by.login, + } + : null, + modifiedBy: data.modified_by + ? { + id: data.modified_by.id, + name: data.modified_by.name, + login: data.modified_by.login, + } + : null, + ownedBy: data.owned_by + ? { + id: data.owned_by.id, + name: data.owned_by.name, + login: data.owned_by.login, + } + : null, + parentId: data.parent?.id ?? null, + parentName: data.parent?.name ?? null, + sharedLink: data.shared_link ?? null, + tags: data.tags ?? [], + commentCount: data.comment_count ?? null, + }, + } + }, + + outputs: FILE_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box/index.ts b/apps/sim/tools/box/index.ts new file mode 100644 index 00000000000..2e3b748fb42 --- /dev/null +++ b/apps/sim/tools/box/index.ts @@ -0,0 +1,10 @@ +export { boxCopyFileTool } from '@/tools/box/copy_file' +export { boxCreateFolderTool } from '@/tools/box/create_folder' +export { boxDeleteFileTool } from '@/tools/box/delete_file' +export { boxDeleteFolderTool } from '@/tools/box/delete_folder' +export { boxDownloadFileTool } from '@/tools/box/download_file' +export { boxGetFileInfoTool } from '@/tools/box/get_file_info' +export { boxListFolderItemsTool } from '@/tools/box/list_folder_items' +export { boxSearchTool } from '@/tools/box/search' +export { boxUpdateFileTool } from '@/tools/box/update_file' +export { boxUploadFileTool } from '@/tools/box/upload_file' diff --git a/apps/sim/tools/box/list_folder_items.ts b/apps/sim/tools/box/list_folder_items.ts new file mode 100644 index 00000000000..26474255842 --- /dev/null +++ b/apps/sim/tools/box/list_folder_items.ts @@ -0,0 +1,98 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxFolderItemsResponse, BoxListFolderItemsParams } from './types' +import { FOLDER_ITEMS_OUTPUT_PROPERTIES } from './types' + +export const boxListFolderItemsTool: ToolConfig = + { + id: 'box_list_folder_items', + name: 'Box List Folder Items', + description: 'List files and folders in a Box folder', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + folderId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the folder to list items from (use "0" for root)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items to return per page', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'The offset for pagination', + }, + sort: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort field: id, name, date, or size', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction: ASC or DESC', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.limit !== undefined) queryParams.set('limit', String(params.limit)) + if (params.offset !== undefined) queryParams.set('offset', String(params.offset)) + if (params.sort) queryParams.set('sort', params.sort) + if (params.direction) queryParams.set('direction', params.direction) + const qs = queryParams.toString() + return `https://api.box.com/2.0/folders/${params.folderId.trim()}/items${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box API error: ${response.status}`) + } + + return { + success: true, + output: { + items: (data.entries ?? []).map((item: Record) => ({ + type: item.type ?? '', + id: item.id ?? '', + name: item.name ?? '', + size: item.size ?? null, + createdAt: item.created_at ?? null, + modifiedAt: item.modified_at ?? null, + })), + totalCount: data.total_count ?? 0, + offset: data.offset ?? 0, + limit: data.limit ?? 0, + }, + } + }, + + outputs: FOLDER_ITEMS_OUTPUT_PROPERTIES, + } diff --git a/apps/sim/tools/box/search.ts b/apps/sim/tools/box/search.ts new file mode 100644 index 00000000000..942471b276a --- /dev/null +++ b/apps/sim/tools/box/search.ts @@ -0,0 +1,105 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxSearchParams, BoxSearchResponse } from './types' +import { SEARCH_RESULT_OUTPUT_PROPERTIES } from './types' + +export const boxSearchTool: ToolConfig = { + id: 'box_search', + name: 'Box Search', + description: 'Search for files and folders in Box', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The search query string', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return', + }, + offset: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'The offset for pagination', + }, + ancestorFolderId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Restrict search to a specific folder and its subfolders', + }, + fileExtensions: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated file extensions to filter by (e.g., pdf,docx)', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Restrict to a specific content type: file, folder, or web_link', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + queryParams.set('query', params.query) + if (params.limit !== undefined) queryParams.set('limit', String(params.limit)) + if (params.offset !== undefined) queryParams.set('offset', String(params.offset)) + if (params.ancestorFolderId) + queryParams.set('ancestor_folder_ids', params.ancestorFolderId.trim()) + if (params.fileExtensions) queryParams.set('file_extensions', params.fileExtensions) + if (params.type) queryParams.set('type', params.type) + return `https://api.box.com/2.0/search?${queryParams.toString()}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box API error: ${response.status}`) + } + + return { + success: true, + output: { + results: (data.entries ?? []).map((item: Record) => ({ + type: item.type ?? '', + id: item.id ?? '', + name: item.name ?? '', + size: item.size ?? null, + createdAt: item.created_at ?? null, + modifiedAt: item.modified_at ?? null, + parentId: (item.parent as Record | undefined)?.id ?? null, + parentName: (item.parent as Record | undefined)?.name ?? null, + })), + totalCount: data.total_count ?? 0, + }, + } + }, + + outputs: SEARCH_RESULT_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box/types.ts b/apps/sim/tools/box/types.ts new file mode 100644 index 00000000000..d370a1ac75f --- /dev/null +++ b/apps/sim/tools/box/types.ts @@ -0,0 +1,251 @@ +import type { OutputProperty, ToolResponse } from '@/tools/types' + +export interface BoxUploadFileParams { + accessToken: string + parentFolderId: string + file?: unknown + fileContent?: string + fileName?: string +} + +export interface BoxDownloadFileParams { + accessToken: string + fileId: string +} + +export interface BoxGetFileInfoParams { + accessToken: string + fileId: string +} + +export interface BoxListFolderItemsParams { + accessToken: string + folderId: string + limit?: number + offset?: number + sort?: string + direction?: string +} + +export interface BoxCreateFolderParams { + accessToken: string + name: string + parentFolderId: string +} + +export interface BoxDeleteFileParams { + accessToken: string + fileId: string +} + +export interface BoxDeleteFolderParams { + accessToken: string + folderId: string + recursive?: boolean +} + +export interface BoxCopyFileParams { + accessToken: string + fileId: string + parentFolderId: string + name?: string +} + +export interface BoxSearchParams { + accessToken: string + query: string + limit?: number + offset?: number + ancestorFolderId?: string + fileExtensions?: string + type?: string +} + +export interface BoxUpdateFileParams { + accessToken: string + fileId: string + name?: string + description?: string + parentFolderId?: string + tags?: string +} + +export interface BoxUploadFileResponse extends ToolResponse { + output: { + id: string + name: string + size: number + sha1: string | null + createdAt: string | null + modifiedAt: string | null + parentId: string | null + parentName: string | null + } +} + +export interface BoxDownloadFileResponse extends ToolResponse { + output: { + file: { + name: string + mimeType: string + data: string + size: number + } + content: string + } +} + +export interface BoxFileInfoResponse extends ToolResponse { + output: { + id: string + name: string + description: string | null + size: number + sha1: string | null + createdAt: string | null + modifiedAt: string | null + createdBy: { id: string; name: string; login: string } | null + modifiedBy: { id: string; name: string; login: string } | null + ownedBy: { id: string; name: string; login: string } | null + parentId: string | null + parentName: string | null + sharedLink: Record | null + tags: string[] + commentCount: number | null + } +} + +export interface BoxFolderItemsResponse extends ToolResponse { + output: { + items: Array> + totalCount: number + offset: number + limit: number + } +} + +export interface BoxFolderResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + modifiedAt: string | null + parentId: string | null + parentName: string | null + } +} + +export interface BoxSearchResponse extends ToolResponse { + output: { + results: Array> + totalCount: number + } +} + +const USER_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'User ID' }, + name: { type: 'string', description: 'User name' }, + login: { type: 'string', description: 'User email/login' }, +} as const satisfies Record + +export const FILE_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'File ID' }, + name: { type: 'string', description: 'File name' }, + description: { type: 'string', description: 'File description', optional: true }, + size: { type: 'number', description: 'File size in bytes' }, + sha1: { type: 'string', description: 'SHA1 hash of file content', optional: true }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, + createdBy: { + type: 'object', + description: 'User who created the file', + optional: true, + properties: USER_OUTPUT_PROPERTIES, + }, + modifiedBy: { + type: 'object', + description: 'User who last modified the file', + optional: true, + properties: USER_OUTPUT_PROPERTIES, + }, + ownedBy: { + type: 'object', + description: 'User who owns the file', + optional: true, + properties: USER_OUTPUT_PROPERTIES, + }, + parentId: { type: 'string', description: 'Parent folder ID', optional: true }, + parentName: { type: 'string', description: 'Parent folder name', optional: true }, + sharedLink: { type: 'json', description: 'Shared link details', optional: true }, + tags: { + type: 'array', + description: 'File tags', + items: { type: 'string' }, + optional: true, + }, + commentCount: { type: 'number', description: 'Number of comments', optional: true }, +} as const satisfies Record + +export const FOLDER_ITEM_OUTPUT_PROPERTIES = { + type: { type: 'string', description: 'Item type (file, folder, web_link)' }, + id: { type: 'string', description: 'Item ID' }, + name: { type: 'string', description: 'Item name' }, + size: { type: 'number', description: 'Item size in bytes', optional: true }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, +} as const satisfies Record + +export const FOLDER_ITEMS_OUTPUT_PROPERTIES = { + items: { + type: 'array', + description: 'List of items in the folder', + items: { + type: 'object', + properties: FOLDER_ITEM_OUTPUT_PROPERTIES, + }, + }, + totalCount: { type: 'number', description: 'Total number of items in the folder' }, + offset: { type: 'number', description: 'Current pagination offset' }, + limit: { type: 'number', description: 'Current pagination limit' }, +} as const satisfies Record + +export const FOLDER_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'Folder ID' }, + name: { type: 'string', description: 'Folder name' }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, + parentId: { type: 'string', description: 'Parent folder ID', optional: true }, + parentName: { type: 'string', description: 'Parent folder name', optional: true }, +} as const satisfies Record + +export const SEARCH_RESULT_OUTPUT_PROPERTIES = { + results: { + type: 'array', + description: 'Search results', + items: { + type: 'object', + properties: { + type: { type: 'string', description: 'Item type (file, folder, web_link)' }, + id: { type: 'string', description: 'Item ID' }, + name: { type: 'string', description: 'Item name' }, + size: { type: 'number', description: 'Item size in bytes', optional: true }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, + parentId: { type: 'string', description: 'Parent folder ID', optional: true }, + parentName: { type: 'string', description: 'Parent folder name', optional: true }, + }, + }, + }, + totalCount: { type: 'number', description: 'Total number of matching results' }, +} as const satisfies Record + +export const UPLOAD_FILE_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'Uploaded file ID' }, + name: { type: 'string', description: 'Uploaded file name' }, + size: { type: 'number', description: 'File size in bytes' }, + sha1: { type: 'string', description: 'SHA1 hash of file content', optional: true }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, + parentId: { type: 'string', description: 'Parent folder ID', optional: true }, + parentName: { type: 'string', description: 'Parent folder name', optional: true }, +} as const satisfies Record diff --git a/apps/sim/tools/box/update_file.ts b/apps/sim/tools/box/update_file.ts new file mode 100644 index 00000000000..f44446f9a1a --- /dev/null +++ b/apps/sim/tools/box/update_file.ts @@ -0,0 +1,120 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxFileInfoResponse, BoxUpdateFileParams } from './types' +import { FILE_OUTPUT_PROPERTIES } from './types' + +export const boxUpdateFileTool: ToolConfig = { + id: 'box_update_file', + name: 'Box Update File', + description: 'Update file info in Box (rename, move, change description, add tags)', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + fileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the file to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the file', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New description for the file (max 256 characters)', + }, + parentFolderId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Move the file to a different folder by specifying the folder ID', + }, + tags: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated tags to set on the file', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/files/${params.fileId.trim()}`, + method: 'PUT', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + if (params.description !== undefined) body.description = params.description + if (params.parentFolderId) body.parent = { id: params.parentFolderId.trim() } + if (params.tags) body.tags = params.tags.split(',').map((t: string) => t.trim()) + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + name: data.name ?? '', + description: data.description ?? null, + size: data.size ?? 0, + sha1: data.sha1 ?? null, + createdAt: data.created_at ?? null, + modifiedAt: data.modified_at ?? null, + createdBy: data.created_by + ? { + id: data.created_by.id, + name: data.created_by.name, + login: data.created_by.login, + } + : null, + modifiedBy: data.modified_by + ? { + id: data.modified_by.id, + name: data.modified_by.name, + login: data.modified_by.login, + } + : null, + ownedBy: data.owned_by + ? { + id: data.owned_by.id, + name: data.owned_by.name, + login: data.owned_by.login, + } + : null, + parentId: data.parent?.id ?? null, + parentName: data.parent?.name ?? null, + sharedLink: data.shared_link ?? null, + tags: data.tags ?? [], + commentCount: data.comment_count ?? null, + }, + } + }, + + outputs: FILE_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box/upload_file.ts b/apps/sim/tools/box/upload_file.ts new file mode 100644 index 00000000000..efa98949ff8 --- /dev/null +++ b/apps/sim/tools/box/upload_file.ts @@ -0,0 +1,78 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxUploadFileParams, BoxUploadFileResponse } from './types' +import { UPLOAD_FILE_OUTPUT_PROPERTIES } from './types' + +export const boxUploadFileTool: ToolConfig = { + id: 'box_upload_file', + name: 'Box Upload File', + description: 'Upload a file to a Box folder', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + parentFolderId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the folder to upload the file to (use "0" for root)', + }, + file: { + type: 'file', + required: false, + visibility: 'user-or-llm', + description: 'The file to upload (UserFile object)', + }, + fileContent: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'Legacy: base64 encoded file content', + }, + fileName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional filename override', + }, + }, + + request: { + url: '/api/tools/box/upload', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + accessToken: params.accessToken, + parentFolderId: params.parentFolderId, + file: params.file, + fileContent: params.fileContent, + fileName: params.fileName, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!data.success) { + throw new Error(data.error || 'Failed to upload file') + } + + return { + success: true, + output: data.output, + } + }, + + outputs: UPLOAD_FILE_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box_sign/cancel_request.ts b/apps/sim/tools/box_sign/cancel_request.ts new file mode 100644 index 00000000000..aa342bfec46 --- /dev/null +++ b/apps/sim/tools/box_sign/cancel_request.ts @@ -0,0 +1,91 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxSignCancelRequestParams, BoxSignResponse } from './types' +import { SIGN_REQUEST_OUTPUT_PROPERTIES } from './types' + +export const boxSignCancelRequestTool: ToolConfig = { + id: 'box_sign_cancel_request', + name: 'Box Sign Cancel Request', + description: 'Cancel a pending Box Sign request', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + signRequestId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the sign request to cancel', + }, + reason: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional reason for cancellation', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/sign_requests/${params.signRequestId}/cancel`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + if (params.reason) { + return { reason: params.reason } + } + return {} + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box Sign API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? '', + name: data.name ?? null, + shortId: data.short_id ?? null, + signers: (data.signers ?? []).map((s: Record) => ({ + email: s.email ?? null, + role: s.role ?? null, + hasViewedDocument: s.has_viewed_document ?? null, + signerDecision: s.signer_decision ?? null, + embedUrl: s.embed_url ?? null, + order: s.order ?? null, + })), + sourceFiles: (data.source_files ?? []).map((f: Record) => ({ + id: f.id ?? null, + type: f.type ?? null, + name: f.name ?? null, + })), + emailSubject: data.email_subject ?? null, + emailMessage: data.email_message ?? null, + daysValid: data.days_valid ?? null, + createdAt: data.created_at ?? null, + autoExpireAt: data.auto_expire_at ?? null, + prepareUrl: data.prepare_url ?? null, + senderEmail: data.sender_email ?? null, + }, + } + }, + + outputs: SIGN_REQUEST_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box_sign/create_request.ts b/apps/sim/tools/box_sign/create_request.ts new file mode 100644 index 00000000000..24dbb6ea48d --- /dev/null +++ b/apps/sim/tools/box_sign/create_request.ts @@ -0,0 +1,220 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxSignCreateRequestParams, BoxSignResponse } from './types' +import { SIGN_REQUEST_OUTPUT_PROPERTIES } from './types' + +export const boxSignCreateRequestTool: ToolConfig = { + id: 'box_sign_create_request', + name: 'Box Sign Create Request', + description: 'Create a new Box Sign request to send documents for e-signature', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + sourceFileIds: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated Box file IDs to send for signing', + }, + signerEmail: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Primary signer email address', + }, + signerRole: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Primary signer role: signer, approver, or final_copy_reader (default: signer)', + }, + additionalSigners: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'JSON array of additional signers, e.g. [{"email":"user@example.com","role":"signer"}]', + }, + parentFolderId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Box folder ID where signed documents will be stored (default: user root)', + }, + emailSubject: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom subject line for the signing email', + }, + emailMessage: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom message in the signing email body', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name for the sign request', + }, + daysValid: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of days before the request expires (0-730)', + }, + areRemindersEnabled: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to send automatic signing reminders', + }, + areTextSignaturesEnabled: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to allow typed (text) signatures', + }, + signatureColor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Signature color: blue, black, or red', + }, + redirectUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL to redirect signers to after signing', + }, + declinedRedirectUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL to redirect signers to after declining', + }, + isDocumentPreparationNeeded: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether document preparation is needed before sending', + }, + externalId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External system reference ID', + }, + }, + + request: { + url: 'https://api.box.com/2.0/sign_requests', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const fileIds = params.sourceFileIds.split(',').map((id: string) => id.trim()) + const sourceFiles = fileIds.map((id: string) => ({ type: 'file', id })) + + const signers: Array> = [ + { + email: params.signerEmail, + role: params.signerRole || 'signer', + }, + ] + + if (params.additionalSigners) { + try { + const additional = + typeof params.additionalSigners === 'string' + ? JSON.parse(params.additionalSigners) + : params.additionalSigners + if (Array.isArray(additional)) { + signers.push(...additional) + } + } catch { + // Ignore parse errors for additional signers + } + } + + const body: Record = { + source_files: sourceFiles, + signers, + } + + if (params.parentFolderId) { + body.parent_folder = { type: 'folder', id: params.parentFolderId } + } + if (params.emailSubject) body.email_subject = params.emailSubject + if (params.emailMessage) body.email_message = params.emailMessage + if (params.name) body.name = params.name + if (params.daysValid !== undefined) body.days_valid = params.daysValid + if (params.areRemindersEnabled !== undefined) + body.are_reminders_enabled = params.areRemindersEnabled + if (params.areTextSignaturesEnabled !== undefined) + body.are_text_signatures_enabled = params.areTextSignaturesEnabled + if (params.signatureColor) body.signature_color = params.signatureColor + if (params.redirectUrl) body.redirect_url = params.redirectUrl + if (params.declinedRedirectUrl) body.declined_redirect_url = params.declinedRedirectUrl + if (params.isDocumentPreparationNeeded !== undefined) + body.is_document_preparation_needed = params.isDocumentPreparationNeeded + if (params.externalId) body.external_id = params.externalId + + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box Sign API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? '', + name: data.name ?? null, + shortId: data.short_id ?? null, + signers: (data.signers ?? []).map((s: Record) => ({ + email: s.email ?? null, + role: s.role ?? null, + hasViewedDocument: s.has_viewed_document ?? null, + signerDecision: s.signer_decision ?? null, + embedUrl: s.embed_url ?? null, + order: s.order ?? null, + })), + sourceFiles: (data.source_files ?? []).map((f: Record) => ({ + id: f.id ?? null, + type: f.type ?? null, + name: f.name ?? null, + })), + emailSubject: data.email_subject ?? null, + emailMessage: data.email_message ?? null, + daysValid: data.days_valid ?? null, + createdAt: data.created_at ?? null, + autoExpireAt: data.auto_expire_at ?? null, + prepareUrl: data.prepare_url ?? null, + senderEmail: data.sender_email ?? null, + }, + } + }, + + outputs: SIGN_REQUEST_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box_sign/get_request.ts b/apps/sim/tools/box_sign/get_request.ts new file mode 100644 index 00000000000..e1819658d1d --- /dev/null +++ b/apps/sim/tools/box_sign/get_request.ts @@ -0,0 +1,78 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxSignGetRequestParams, BoxSignResponse } from './types' +import { SIGN_REQUEST_OUTPUT_PROPERTIES } from './types' + +export const boxSignGetRequestTool: ToolConfig = { + id: 'box_sign_get_request', + name: 'Box Sign Get Request', + description: 'Get the details and status of a Box Sign request', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + signRequestId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the sign request to retrieve', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/sign_requests/${params.signRequestId}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box Sign API error: ${response.status}`) + } + + return { + success: true, + output: { + id: data.id ?? '', + status: data.status ?? '', + name: data.name ?? null, + shortId: data.short_id ?? null, + signers: (data.signers ?? []).map((s: Record) => ({ + email: s.email ?? null, + role: s.role ?? null, + hasViewedDocument: s.has_viewed_document ?? null, + signerDecision: s.signer_decision ?? null, + embedUrl: s.embed_url ?? null, + order: s.order ?? null, + })), + sourceFiles: (data.source_files ?? []).map((f: Record) => ({ + id: f.id ?? null, + type: f.type ?? null, + name: f.name ?? null, + })), + emailSubject: data.email_subject ?? null, + emailMessage: data.email_message ?? null, + daysValid: data.days_valid ?? null, + createdAt: data.created_at ?? null, + autoExpireAt: data.auto_expire_at ?? null, + prepareUrl: data.prepare_url ?? null, + senderEmail: data.sender_email ?? null, + }, + } + }, + + outputs: SIGN_REQUEST_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box_sign/index.ts b/apps/sim/tools/box_sign/index.ts new file mode 100644 index 00000000000..23b0c3808fd --- /dev/null +++ b/apps/sim/tools/box_sign/index.ts @@ -0,0 +1,5 @@ +export { boxSignCancelRequestTool } from '@/tools/box_sign/cancel_request' +export { boxSignCreateRequestTool } from '@/tools/box_sign/create_request' +export { boxSignGetRequestTool } from '@/tools/box_sign/get_request' +export { boxSignListRequestsTool } from '@/tools/box_sign/list_requests' +export { boxSignResendRequestTool } from '@/tools/box_sign/resend_request' diff --git a/apps/sim/tools/box_sign/list_requests.ts b/apps/sim/tools/box_sign/list_requests.ts new file mode 100644 index 00000000000..0cd396464bb --- /dev/null +++ b/apps/sim/tools/box_sign/list_requests.ts @@ -0,0 +1,100 @@ +import type { ToolConfig } from '@/tools/types' +import type { BoxSignListRequestsParams, BoxSignListResponse } from './types' +import { SIGN_REQUEST_LIST_OUTPUT_PROPERTIES } from './types' + +export const boxSignListRequestsTool: ToolConfig = { + id: 'box_sign_list_requests', + name: 'Box Sign List Requests', + description: 'List all Box Sign requests', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of sign requests to return (max 1000)', + }, + marker: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination marker from a previous response', + }, + }, + + request: { + url: (params) => { + const queryParams = new URLSearchParams() + if (params.limit !== undefined) queryParams.set('limit', String(params.limit)) + if (params.marker) queryParams.set('marker', params.marker) + const qs = queryParams.toString() + return `https://api.box.com/2.0/sign_requests${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || `Box Sign API error: ${response.status}`) + } + + const entries = data.entries ?? [] + + return { + success: true, + output: { + signRequests: entries.map((req: Record) => ({ + id: req.id ?? '', + status: req.status ?? '', + name: req.name ?? null, + shortId: req.short_id ?? null, + signers: ((req.signers as Array> | undefined) ?? []).map( + (s: Record) => ({ + email: s.email ?? null, + role: s.role ?? null, + hasViewedDocument: s.has_viewed_document ?? null, + signerDecision: s.signer_decision ?? null, + embedUrl: s.embed_url ?? null, + order: s.order ?? null, + }) + ), + sourceFiles: ((req.source_files as Array> | undefined) ?? []).map( + (f: Record) => ({ + id: f.id ?? null, + type: f.type ?? null, + name: f.name ?? null, + }) + ), + emailSubject: req.email_subject ?? null, + emailMessage: req.email_message ?? null, + daysValid: req.days_valid ?? null, + createdAt: req.created_at ?? null, + autoExpireAt: req.auto_expire_at ?? null, + prepareUrl: req.prepare_url ?? null, + senderEmail: req.sender_email ?? null, + })), + count: entries.length, + nextMarker: data.next_marker ?? null, + }, + } + }, + + outputs: SIGN_REQUEST_LIST_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/box_sign/resend_request.ts b/apps/sim/tools/box_sign/resend_request.ts new file mode 100644 index 00000000000..39e9da13fa8 --- /dev/null +++ b/apps/sim/tools/box_sign/resend_request.ts @@ -0,0 +1,55 @@ +import type { ToolConfig, ToolResponse } from '@/tools/types' +import type { BoxSignResendRequestParams } from './types' + +export const boxSignResendRequestTool: ToolConfig = { + id: 'box_sign_resend_request', + name: 'Box Sign Resend Request', + description: 'Resend a Box Sign request to signers who have not yet signed', + version: '1.0.0', + + oauth: { + required: true, + provider: 'box', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Box API', + }, + signRequestId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the sign request to resend', + }, + }, + + request: { + url: (params) => `https://api.box.com/2.0/sign_requests/${params.signRequestId}/resend`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const data = await response.json() + throw new Error(data.message || `Box Sign API error: ${response.status}`) + } + + return { + success: true, + output: { + message: 'Sign request resent successfully', + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Success confirmation message' }, + }, +} diff --git a/apps/sim/tools/box_sign/types.ts b/apps/sim/tools/box_sign/types.ts new file mode 100644 index 00000000000..c2a6c2affb1 --- /dev/null +++ b/apps/sim/tools/box_sign/types.ts @@ -0,0 +1,163 @@ +import type { OutputProperty, ToolResponse } from '@/tools/types' + +export interface BoxSignCreateRequestParams { + accessToken: string + sourceFileIds: string + signerEmail: string + signerRole?: string + additionalSigners?: string + parentFolderId?: string + emailSubject?: string + emailMessage?: string + name?: string + daysValid?: number + areRemindersEnabled?: boolean + areTextSignaturesEnabled?: boolean + signatureColor?: string + redirectUrl?: string + declinedRedirectUrl?: string + isDocumentPreparationNeeded?: boolean + externalId?: string +} + +export interface BoxSignGetRequestParams { + accessToken: string + signRequestId: string +} + +export interface BoxSignListRequestsParams { + accessToken: string + limit?: number + marker?: string +} + +export interface BoxSignCancelRequestParams { + accessToken: string + signRequestId: string + reason?: string +} + +export interface BoxSignResendRequestParams { + accessToken: string + signRequestId: string +} + +export interface BoxSignResponse extends ToolResponse { + output: { + id: string + status: string + name: string | null + shortId: string | null + signers: Array> + sourceFiles: Array> + emailSubject: string | null + emailMessage: string | null + daysValid: number | null + createdAt: string | null + autoExpireAt: string | null + prepareUrl: string | null + senderEmail: string | null + } +} + +export interface BoxSignListResponse extends ToolResponse { + output: { + signRequests: Array> + count: number + nextMarker: string | null + } +} + +const SIGNER_OUTPUT_PROPERTIES = { + email: { type: 'string', description: 'Signer email address' }, + role: { type: 'string', description: 'Signer role (signer, approver, final_copy_reader)' }, + hasViewedDocument: { + type: 'boolean', + description: 'Whether the signer has viewed the document', + optional: true, + }, + signerDecision: { + type: 'json', + description: 'Signer decision details (type, finalized_at, additional_info)', + optional: true, + }, + embedUrl: { + type: 'string', + description: 'URL for embedded signing experience', + optional: true, + }, + order: { type: 'number', description: 'Order in signing sequence', optional: true }, +} as const satisfies Record + +const SOURCE_FILE_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'File ID' }, + type: { type: 'string', description: 'File type' }, + name: { type: 'string', description: 'File name', optional: true }, +} as const satisfies Record + +export const SIGN_REQUEST_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'Sign request ID' }, + status: { + type: 'string', + description: + 'Request status (converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing)', + }, + name: { type: 'string', description: 'Sign request name', optional: true }, + shortId: { type: 'string', description: 'Human-readable short ID', optional: true }, + signers: { + type: 'array', + description: 'List of signers', + items: { + type: 'object', + properties: SIGNER_OUTPUT_PROPERTIES, + }, + }, + sourceFiles: { + type: 'array', + description: 'Source files for signing', + items: { + type: 'object', + properties: SOURCE_FILE_OUTPUT_PROPERTIES, + }, + }, + emailSubject: { + type: 'string', + description: 'Custom email subject line', + optional: true, + }, + emailMessage: { + type: 'string', + description: 'Custom email message body', + optional: true, + }, + daysValid: { + type: 'number', + description: 'Number of days the request is valid', + optional: true, + }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + autoExpireAt: { type: 'string', description: 'Auto-expiration timestamp', optional: true }, + prepareUrl: { + type: 'string', + description: 'URL for document preparation (if preparation is needed)', + optional: true, + }, + senderEmail: { type: 'string', description: 'Email of the sender', optional: true }, +} as const satisfies Record + +export const SIGN_REQUEST_LIST_OUTPUT_PROPERTIES = { + signRequests: { + type: 'array', + description: 'List of sign requests', + items: { + type: 'object', + properties: SIGN_REQUEST_OUTPUT_PROPERTIES, + }, + }, + count: { type: 'number', description: 'Number of sign requests returned in this page' }, + nextMarker: { + type: 'string', + description: 'Marker for next page of results', + optional: true, + }, +} as const satisfies Record diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 557aca9d2b7..30029d53d94 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -153,6 +153,25 @@ import { attioUpdateTaskTool, attioUpdateWebhookTool, } from '@/tools/attio' +import { + boxCopyFileTool, + boxCreateFolderTool, + boxDeleteFileTool, + boxDeleteFolderTool, + boxDownloadFileTool, + boxGetFileInfoTool, + boxListFolderItemsTool, + boxSearchTool, + boxUpdateFileTool, + boxUploadFileTool, +} from '@/tools/box' +import { + boxSignCancelRequestTool, + boxSignCreateRequestTool, + boxSignGetRequestTool, + boxSignListRequestsTool, + boxSignResendRequestTool, +} from '@/tools/box_sign' import { brandfetchGetBrandTool, brandfetchSearchTool } from '@/tools/brandfetch' import { browserUseRunTaskTool } from '@/tools/browser_use' import { @@ -2409,6 +2428,21 @@ export const tools: Record = { ashby_update_candidate: ashbyUpdateCandidateTool, brandfetch_get_brand: brandfetchGetBrandTool, brandfetch_search: brandfetchSearchTool, + box_copy_file: boxCopyFileTool, + box_create_folder: boxCreateFolderTool, + box_delete_file: boxDeleteFileTool, + box_delete_folder: boxDeleteFolderTool, + box_download_file: boxDownloadFileTool, + box_get_file_info: boxGetFileInfoTool, + box_list_folder_items: boxListFolderItemsTool, + box_search: boxSearchTool, + box_update_file: boxUpdateFileTool, + box_upload_file: boxUploadFileTool, + box_sign_create_request: boxSignCreateRequestTool, + box_sign_get_request: boxSignGetRequestTool, + box_sign_list_requests: boxSignListRequestsTool, + box_sign_cancel_request: boxSignCancelRequestTool, + box_sign_resend_request: boxSignResendRequestTool, browser_use_run_task: browserUseRunTaskTool, openai_embeddings: openAIEmbeddingsTool, http_request: httpRequestTool, From 0d11f9f61c34e290afd050094dc249b4cea9012f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 18:35:51 -0700 Subject: [PATCH 02/16] fix(box): address PR review comments - Fix docsLink for Box Sign: use underscore (box_sign) to match docs URL - Move normalizeFileInput from tool() to params() in Box block config to match Dropbox pattern - Throw error on invalid additionalSigners JSON instead of silently dropping signers Co-Authored-By: Claude Opus 4.6 --- apps/sim/blocks/blocks/box.ts | 6 ++---- apps/sim/blocks/blocks/box_sign.ts | 2 +- apps/sim/tools/box_sign/create_request.ts | 4 +++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/sim/blocks/blocks/box.ts b/apps/sim/blocks/blocks/box.ts index 57a2b193907..8114c06d4bc 100644 --- a/apps/sim/blocks/blocks/box.ts +++ b/apps/sim/blocks/blocks/box.ts @@ -271,16 +271,14 @@ export const BoxBlock: BlockConfig = { 'box_update_file', ], config: { - tool: (params) => { + tool: (params) => `box_${params.operation}`, + params: (params) => { const normalizedFile = normalizeFileInput(params.uploadFile || params.fileRef, { single: true, }) if (normalizedFile) { params.file = normalizedFile } - return `box_${params.operation}` - }, - params: (params) => { const { credential, operation, ...rest } = params const baseParams: Record = { diff --git a/apps/sim/blocks/blocks/box_sign.ts b/apps/sim/blocks/blocks/box_sign.ts index 9b29ab4161a..71eabbc9710 100644 --- a/apps/sim/blocks/blocks/box_sign.ts +++ b/apps/sim/blocks/blocks/box_sign.ts @@ -10,7 +10,7 @@ export const BoxSignBlock: BlockConfig = { 'Send documents for e-signature, check status, and manage sign requests with Box Sign', longDescription: 'Integrate Box Sign into your workflow to send documents for e-signature. Create sign requests with multiple signers, track signing status, list all requests, cancel pending requests, and resend reminders. Ideal for offer letters, contracts, and other documents requiring signatures.', - docsLink: 'https://docs.sim.ai/tools/box-sign', + docsLink: 'https://docs.sim.ai/tools/box_sign', category: 'tools', bgColor: '#0061D5', icon: BoxCompanyIcon, diff --git a/apps/sim/tools/box_sign/create_request.ts b/apps/sim/tools/box_sign/create_request.ts index 24dbb6ea48d..3eddfb418ba 100644 --- a/apps/sim/tools/box_sign/create_request.ts +++ b/apps/sim/tools/box_sign/create_request.ts @@ -147,7 +147,9 @@ export const boxSignCreateRequestTool: ToolConfig Date: Wed, 18 Mar 2026 18:50:48 -0700 Subject: [PATCH 03/16] fix(box): remove unsupported reason param from cancel sign request The Box Sign cancel endpoint (POST /sign_requests/{id}/cancel) does not accept a request body per the API specification. Remove the misleading reason parameter from the tool, types, block config, and docs. Co-Authored-By: Claude Opus 4.6 --- apps/docs/content/docs/en/tools/box_sign.mdx | 2 +- apps/sim/blocks/blocks/box_sign.ts | 10 ---------- apps/sim/tools/box_sign/cancel_request.ts | 13 +------------ apps/sim/tools/box_sign/types.ts | 1 - 4 files changed, 2 insertions(+), 24 deletions(-) diff --git a/apps/docs/content/docs/en/tools/box_sign.mdx b/apps/docs/content/docs/en/tools/box_sign.mdx index 895c38b4ca0..809d40f69b0 100644 --- a/apps/docs/content/docs/en/tools/box_sign.mdx +++ b/apps/docs/content/docs/en/tools/box_sign.mdx @@ -24,6 +24,7 @@ With the Box Sign integration in Sim, you can: The Box Sign integration is ideal for automating document signing workflows such as offer letters, contracts, NDAs, and approval processes. Agents can create sign requests from documents already stored in Box, track completion, and trigger follow-up actions based on signing status. {/* MANUAL-CONTENT-END */} + ## Usage Instructions Integrate Box Sign into your workflow to send documents for e-signature. Create sign requests with multiple signers, track signing status, list all requests, cancel pending requests, and resend reminders. Ideal for offer letters, contracts, and other documents requiring signatures. @@ -144,7 +145,6 @@ Cancel a pending Box Sign request | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `signRequestId` | string | Yes | The ID of the sign request to cancel | -| `reason` | string | No | Optional reason for cancellation | #### Output diff --git a/apps/sim/blocks/blocks/box_sign.ts b/apps/sim/blocks/blocks/box_sign.ts index 71eabbc9710..0a6f7e95bc2 100644 --- a/apps/sim/blocks/blocks/box_sign.ts +++ b/apps/sim/blocks/blocks/box_sign.ts @@ -186,15 +186,6 @@ export const BoxSignBlock: BlockConfig = { }, }, - // Cancel-specific fields - { - id: 'reason', - title: 'Cancellation Reason', - type: 'short-input', - placeholder: 'Optional reason for cancellation', - condition: { field: 'operation', value: 'cancel_request' }, - }, - // List fields { id: 'limit', @@ -259,7 +250,6 @@ export const BoxSignBlock: BlockConfig = { break case 'cancel_request': baseParams.signRequestId = rest.signRequestId - if (rest.reason) baseParams.reason = rest.reason break case 'list_requests': if (rest.limit) baseParams.limit = Number(rest.limit) diff --git a/apps/sim/tools/box_sign/cancel_request.ts b/apps/sim/tools/box_sign/cancel_request.ts index aa342bfec46..8e318fb195f 100644 --- a/apps/sim/tools/box_sign/cancel_request.ts +++ b/apps/sim/tools/box_sign/cancel_request.ts @@ -26,12 +26,6 @@ export const boxSignCancelRequestTool: ToolConfig { - if (params.reason) { - return { reason: params.reason } - } - return {} - }, + body: () => ({}), }, transformResponse: async (response) => { diff --git a/apps/sim/tools/box_sign/types.ts b/apps/sim/tools/box_sign/types.ts index c2a6c2affb1..e59f2c79e78 100644 --- a/apps/sim/tools/box_sign/types.ts +++ b/apps/sim/tools/box_sign/types.ts @@ -34,7 +34,6 @@ export interface BoxSignListRequestsParams { export interface BoxSignCancelRequestParams { accessToken: string signRequestId: string - reason?: string } export interface BoxSignResendRequestParams { From 6e5cd21ab0f8c029e4dad548b474865566be5593 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 19:01:15 -0700 Subject: [PATCH 04/16] fix(box): use canonical param ID for file normalization in params() The params function must reference canonical IDs (params.file), not raw subBlock IDs (uploadFile/fileRef) which are deleted after canonical transformation. Matches the Dropbox block pattern. Co-Authored-By: Claude Opus 4.6 --- apps/sim/blocks/blocks/box.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/sim/blocks/blocks/box.ts b/apps/sim/blocks/blocks/box.ts index 8114c06d4bc..f74d0047516 100644 --- a/apps/sim/blocks/blocks/box.ts +++ b/apps/sim/blocks/blocks/box.ts @@ -273,9 +273,7 @@ export const BoxBlock: BlockConfig = { config: { tool: (params) => `box_${params.operation}`, params: (params) => { - const normalizedFile = normalizeFileInput(params.uploadFile || params.fileRef, { - single: true, - }) + const normalizedFile = normalizeFileInput(params.file, { single: true }) if (normalizedFile) { params.file = normalizedFile } From 68b2302d98b9eaa9c264289fe11de0b092b7880f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 21:22:34 -0700 Subject: [PATCH 05/16] fix(box): use generic output descriptions for shared file properties Rename "Uploaded file ID/name" to "File ID/name" in UPLOAD_FILE_OUTPUT_PROPERTIES since the constant is shared by both upload and copy operations. Co-Authored-By: Claude Opus 4.6 --- apps/docs/content/docs/en/tools/box.mdx | 9 +++++---- apps/sim/tools/box/types.ts | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/docs/content/docs/en/tools/box.mdx b/apps/docs/content/docs/en/tools/box.mdx index f99ffda4a40..66dd2a7cdb6 100644 --- a/apps/docs/content/docs/en/tools/box.mdx +++ b/apps/docs/content/docs/en/tools/box.mdx @@ -28,6 +28,7 @@ With the Box integration in Sim, you can: These capabilities allow your Sim agents to automate Box operations directly within your workflows — from organizing documents and distributing content to processing uploaded files and maintaining structured cloud storage as part of your business processes. {/* MANUAL-CONTENT-END */} + ## Usage Instructions Integrate Box into your workflow to manage files and folders. Upload and download files, get file information, list folder contents, create and delete folders, copy files, search across your Box account, and update file metadata. @@ -53,8 +54,8 @@ Upload a file to a Box folder | Parameter | Type | Description | | --------- | ---- | ----------- | -| `id` | string | Uploaded file ID | -| `name` | string | Uploaded file name | +| `id` | string | File ID | +| `name` | string | File name | | `size` | number | File size in bytes | | `sha1` | string | SHA1 hash of file content | | `createdAt` | string | Creation timestamp | @@ -204,8 +205,8 @@ Copy a file to another folder in Box | Parameter | Type | Description | | --------- | ---- | ----------- | -| `id` | string | Uploaded file ID | -| `name` | string | Uploaded file name | +| `id` | string | File ID | +| `name` | string | File name | | `size` | number | File size in bytes | | `sha1` | string | SHA1 hash of file content | | `createdAt` | string | Creation timestamp | diff --git a/apps/sim/tools/box/types.ts b/apps/sim/tools/box/types.ts index d370a1ac75f..897984d1619 100644 --- a/apps/sim/tools/box/types.ts +++ b/apps/sim/tools/box/types.ts @@ -240,8 +240,8 @@ export const SEARCH_RESULT_OUTPUT_PROPERTIES = { } as const satisfies Record export const UPLOAD_FILE_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Uploaded file ID' }, - name: { type: 'string', description: 'Uploaded file name' }, + id: { type: 'string', description: 'File ID' }, + name: { type: 'string', description: 'File name' }, size: { type: 'number', description: 'File size in bytes' }, sha1: { type: 'string', description: 'SHA1 hash of file content', optional: true }, createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, From 3b6900236f000102034b003007233d537c61c23a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 21:37:15 -0700 Subject: [PATCH 06/16] fix(box): rename items output to entries for list_folder_items Rename the output field from "items" to "entries" to match Box API naming and avoid collision with JSON schema "items" keyword that prevented the docs generator from rendering the nested array structure. Co-Authored-By: Claude Opus 4.6 --- apps/docs/content/docs/en/tools/box.mdx | 7 +++++++ apps/sim/blocks/blocks/box.ts | 2 +- apps/sim/tools/box/list_folder_items.ts | 2 +- apps/sim/tools/box/types.ts | 22 ++++++++++------------ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/apps/docs/content/docs/en/tools/box.mdx b/apps/docs/content/docs/en/tools/box.mdx index 66dd2a7cdb6..26be13d5ae1 100644 --- a/apps/docs/content/docs/en/tools/box.mdx +++ b/apps/docs/content/docs/en/tools/box.mdx @@ -128,6 +128,13 @@ List files and folders in a Box folder | Parameter | Type | Description | | --------- | ---- | ----------- | +| `entries` | array | List of items in the folder | +| ↳ `type` | string | Item type \(file, folder, web_link\) | +| ↳ `id` | string | Item ID | +| ↳ `name` | string | Item name | +| ↳ `size` | number | Item size in bytes | +| ↳ `createdAt` | string | Creation timestamp | +| ↳ `modifiedAt` | string | Last modified timestamp | | `totalCount` | number | Total number of items in the folder | | `offset` | number | Current pagination offset | | `limit` | number | Current pagination limit | diff --git a/apps/sim/blocks/blocks/box.ts b/apps/sim/blocks/blocks/box.ts index f74d0047516..7e2f0e004fc 100644 --- a/apps/sim/blocks/blocks/box.ts +++ b/apps/sim/blocks/blocks/box.ts @@ -364,7 +364,7 @@ export const BoxBlock: BlockConfig = { commentCount: 'number', file: 'file', content: 'string', - items: 'json', + entries: 'json', totalCount: 'number', offset: 'number', limit: 'number', diff --git a/apps/sim/tools/box/list_folder_items.ts b/apps/sim/tools/box/list_folder_items.ts index 26474255842..fca1e1e18c0 100644 --- a/apps/sim/tools/box/list_folder_items.ts +++ b/apps/sim/tools/box/list_folder_items.ts @@ -79,7 +79,7 @@ export const boxListFolderItemsTool: ToolConfig) => ({ + entries: (data.entries ?? []).map((item: Record) => ({ type: item.type ?? '', id: item.id ?? '', name: item.name ?? '', diff --git a/apps/sim/tools/box/types.ts b/apps/sim/tools/box/types.ts index 897984d1619..07368f79984 100644 --- a/apps/sim/tools/box/types.ts +++ b/apps/sim/tools/box/types.ts @@ -117,7 +117,7 @@ export interface BoxFileInfoResponse extends ToolResponse { export interface BoxFolderItemsResponse extends ToolResponse { output: { - items: Array> + entries: Array> totalCount: number offset: number limit: number @@ -186,22 +186,20 @@ export const FILE_OUTPUT_PROPERTIES = { commentCount: { type: 'number', description: 'Number of comments', optional: true }, } as const satisfies Record -export const FOLDER_ITEM_OUTPUT_PROPERTIES = { - type: { type: 'string', description: 'Item type (file, folder, web_link)' }, - id: { type: 'string', description: 'Item ID' }, - name: { type: 'string', description: 'Item name' }, - size: { type: 'number', description: 'Item size in bytes', optional: true }, - createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, - modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, -} as const satisfies Record - export const FOLDER_ITEMS_OUTPUT_PROPERTIES = { - items: { + entries: { type: 'array', description: 'List of items in the folder', items: { type: 'object', - properties: FOLDER_ITEM_OUTPUT_PROPERTIES, + properties: { + type: { type: 'string', description: 'Item type (file, folder, web_link)' }, + id: { type: 'string', description: 'Item ID' }, + name: { type: 'string', description: 'Item name' }, + size: { type: 'number', description: 'Item size in bytes', optional: true }, + createdAt: { type: 'string', description: 'Creation timestamp', optional: true }, + modifiedAt: { type: 'string', description: 'Last modified timestamp', optional: true }, + }, }, }, totalCount: { type: 'number', description: 'Total number of items in the folder' }, From a9ce3ea0a9f929cf2f241d28ae41cab38f2072c2 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 21:50:51 -0700 Subject: [PATCH 07/16] fix(box): filter empty file IDs from sourceFileIds input Add .filter(Boolean) after splitting sourceFileIds to prevent empty strings from trailing/double commas being sent as invalid file IDs to the Box Sign API. Co-Authored-By: Claude Opus 4.6 --- apps/sim/tools/box_sign/create_request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/tools/box_sign/create_request.ts b/apps/sim/tools/box_sign/create_request.ts index 3eddfb418ba..a79856ad134 100644 --- a/apps/sim/tools/box_sign/create_request.ts +++ b/apps/sim/tools/box_sign/create_request.ts @@ -127,7 +127,7 @@ export const boxSignCreateRequestTool: ToolConfig { - const fileIds = params.sourceFileIds.split(',').map((id: string) => id.trim()) + const fileIds = params.sourceFileIds.split(',').map((id: string) => id.trim()).filter(Boolean) const sourceFiles = fileIds.map((id: string) => ({ type: 'file', id })) const signers: Array> = [ From d3bbb51f6c1a01c1a39c0ebd8702d885abe69968 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 21:58:20 -0700 Subject: [PATCH 08/16] refactor(box): merge Box Sign into single Box block Combine Box and Box Sign into one unified block with all 15 operations accessible via a single dropdown, removing the separate box_sign block. Co-Authored-By: Claude Opus 4.6 --- apps/docs/components/ui/icon-mapping.ts | 13 +- apps/docs/content/docs/en/tools/box.mdx | 160 +++++++++- apps/docs/content/docs/en/tools/box_sign.mdx | 183 ------------ apps/docs/content/docs/en/tools/meta.json | 3 +- apps/sim/blocks/blocks/box.ts | 234 ++++++++++++++- apps/sim/blocks/blocks/box_sign.ts | 292 ------------------- apps/sim/blocks/registry.ts | 2 - 7 files changed, 392 insertions(+), 495 deletions(-) delete mode 100644 apps/docs/content/docs/en/tools/box_sign.mdx delete mode 100644 apps/sim/blocks/blocks/box_sign.ts diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 67b216a99e9..cb759febd5f 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -49,8 +49,8 @@ import { FirecrawlIcon, FirefliesIcon, GammaIcon, - GithubIcon, GitLabIcon, + GithubIcon, GmailIcon, GongIcon, GoogleAdsIcon, @@ -93,9 +93,9 @@ import { LinkupIcon, LoopsIcon, LumaIcon, + MailServerIcon, MailchimpIcon, MailgunIcon, - MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -130,6 +130,8 @@ import { ResendIcon, RevenueCatIcon, S3Icon, + SQSIcon, + STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -141,19 +143,17 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, - SQSIcon, SshIcon, - STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, + TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, - TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -164,11 +164,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, - xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, + xIcon, } from '@/components/icons' type IconComponent = ComponentType> @@ -187,7 +187,6 @@ export const blockTypeToIconMap: Record = { ashby: AshbyIcon, attio: AttioIcon, box: BoxCompanyIcon, - box_sign: BoxCompanyIcon, brandfetch: BrandfetchIcon, browser_use: BrowserUseIcon, calcom: CalComIcon, diff --git a/apps/docs/content/docs/en/tools/box.mdx b/apps/docs/content/docs/en/tools/box.mdx index 26be13d5ae1..eea5523df56 100644 --- a/apps/docs/content/docs/en/tools/box.mdx +++ b/apps/docs/content/docs/en/tools/box.mdx @@ -1,6 +1,6 @@ --- title: Box -description: Upload, download, search, and manage files and folders in Box +description: Manage files, folders, and e-signatures with Box --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -11,7 +11,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" /> {/* MANUAL-CONTENT-START:intro */} -[Box](https://www.box.com/) is a leading cloud content management and file sharing platform trusted by enterprises worldwide to securely store, manage, and collaborate on files. Box provides robust APIs for automating file operations and integrating with business workflows. +[Box](https://www.box.com/) is a leading cloud content management and file sharing platform trusted by enterprises worldwide to securely store, manage, and collaborate on files. Box provides robust APIs for automating file operations and integrating with business workflows, including [Box Sign](https://www.box.com/esignature) for native e-signatures. With the Box integration in Sim, you can: @@ -24,14 +24,19 @@ With the Box integration in Sim, you can: - **Copy files**: Duplicate files across folders with optional renaming - **Search**: Find files and folders by name, content, extension, or location - **Update file metadata**: Rename, move, add descriptions, or tag files +- **Create sign requests**: Send documents for e-signature with one or more signers +- **Track signing status**: Monitor the progress of sign requests +- **List sign requests**: View all sign requests with marker-based pagination +- **Cancel sign requests**: Cancel pending sign requests that are no longer needed +- **Resend sign reminders**: Send reminder notifications to signers who haven't completed signing -These capabilities allow your Sim agents to automate Box operations directly within your workflows — from organizing documents and distributing content to processing uploaded files and maintaining structured cloud storage as part of your business processes. +These capabilities allow your Sim agents to automate Box operations directly within your workflows — from organizing documents and distributing content to processing uploaded files, managing e-signature workflows for offer letters and contracts, and maintaining structured cloud storage as part of your business processes. {/* MANUAL-CONTENT-END */} ## Usage Instructions -Integrate Box into your workflow to manage files and folders. Upload and download files, get file information, list folder contents, create and delete folders, copy files, search across your Box account, and update file metadata. +Integrate Box into your workflow to manage files, folders, and e-signatures. Upload and download files, search content, create folders, send documents for e-signature, track signing status, and more. @@ -285,4 +290,151 @@ Update file info in Box (rename, move, change description, add tags) | `tags` | array | File tags | | `commentCount` | number | Number of comments | +### `box_sign_create_request` + +Create a new Box Sign request to send documents for e-signature + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `sourceFileIds` | string | Yes | Comma-separated Box file IDs to send for signing | +| `signerEmail` | string | Yes | Primary signer email address | +| `signerRole` | string | No | Primary signer role: signer, approver, or final_copy_reader \(default: signer\) | +| `additionalSigners` | string | No | JSON array of additional signers, e.g. \[\{"email":"user@example.com","role":"signer"\}\] | +| `parentFolderId` | string | No | Box folder ID where signed documents will be stored \(default: user root\) | +| `emailSubject` | string | No | Custom subject line for the signing email | +| `emailMessage` | string | No | Custom message in the signing email body | +| `name` | string | No | Name for the sign request | +| `daysValid` | number | No | Number of days before the request expires \(0-730\) | +| `areRemindersEnabled` | boolean | No | Whether to send automatic signing reminders | +| `areTextSignaturesEnabled` | boolean | No | Whether to allow typed \(text\) signatures | +| `signatureColor` | string | No | Signature color: blue, black, or red | +| `redirectUrl` | string | No | URL to redirect signers to after signing | +| `declinedRedirectUrl` | string | No | URL to redirect signers to after declining | +| `isDocumentPreparationNeeded` | boolean | No | Whether document preparation is needed before sending | +| `externalId` | string | No | External system reference ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Sign request ID | +| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| `name` | string | Sign request name | +| `shortId` | string | Human-readable short ID | +| `signers` | array | List of signers | +| `sourceFiles` | array | Source files for signing | +| `emailSubject` | string | Custom email subject line | +| `emailMessage` | string | Custom email message body | +| `daysValid` | number | Number of days the request is valid | +| `createdAt` | string | Creation timestamp | +| `autoExpireAt` | string | Auto-expiration timestamp | +| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| `senderEmail` | string | Email of the sender | + +### `box_sign_get_request` + +Get the details and status of a Box Sign request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `signRequestId` | string | Yes | The ID of the sign request to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Sign request ID | +| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| `name` | string | Sign request name | +| `shortId` | string | Human-readable short ID | +| `signers` | array | List of signers | +| `sourceFiles` | array | Source files for signing | +| `emailSubject` | string | Custom email subject line | +| `emailMessage` | string | Custom email message body | +| `daysValid` | number | Number of days the request is valid | +| `createdAt` | string | Creation timestamp | +| `autoExpireAt` | string | Auto-expiration timestamp | +| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| `senderEmail` | string | Email of the sender | + +### `box_sign_list_requests` + +List all Box Sign requests + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `limit` | number | No | Maximum number of sign requests to return \(max 1000\) | +| `marker` | string | No | Pagination marker from a previous response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `signRequests` | array | List of sign requests | +| ↳ `id` | string | Sign request ID | +| ↳ `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| ↳ `name` | string | Sign request name | +| ↳ `shortId` | string | Human-readable short ID | +| ↳ `signers` | array | List of signers | +| ↳ `sourceFiles` | array | Source files for signing | +| ↳ `emailSubject` | string | Custom email subject line | +| ↳ `emailMessage` | string | Custom email message body | +| ↳ `daysValid` | number | Number of days the request is valid | +| ↳ `createdAt` | string | Creation timestamp | +| ↳ `autoExpireAt` | string | Auto-expiration timestamp | +| ↳ `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| ↳ `senderEmail` | string | Email of the sender | +| `count` | number | Number of sign requests returned in this page | +| `nextMarker` | string | Marker for next page of results | + +### `box_sign_cancel_request` + +Cancel a pending Box Sign request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `signRequestId` | string | Yes | The ID of the sign request to cancel | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Sign request ID | +| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | +| `name` | string | Sign request name | +| `shortId` | string | Human-readable short ID | +| `signers` | array | List of signers | +| `sourceFiles` | array | Source files for signing | +| `emailSubject` | string | Custom email subject line | +| `emailMessage` | string | Custom email message body | +| `daysValid` | number | Number of days the request is valid | +| `createdAt` | string | Creation timestamp | +| `autoExpireAt` | string | Auto-expiration timestamp | +| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | +| `senderEmail` | string | Email of the sender | + +### `box_sign_resend_request` + +Resend a Box Sign request to signers who have not yet signed + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `signRequestId` | string | Yes | The ID of the sign request to resend | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Success confirmation message | + diff --git a/apps/docs/content/docs/en/tools/box_sign.mdx b/apps/docs/content/docs/en/tools/box_sign.mdx deleted file mode 100644 index 809d40f69b0..00000000000 --- a/apps/docs/content/docs/en/tools/box_sign.mdx +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: Box Sign -description: Send documents for e-signature, check status, and manage sign requests with Box Sign ---- - -import { BlockInfoCard } from "@/components/ui/block-info-card" - - - -{/* MANUAL-CONTENT-START:intro */} -[Box Sign](https://www.box.com/esignature) is Box's native e-signature solution that allows you to send documents for legally binding electronic signatures directly from your Box account. It's built into Box, so signed documents are automatically stored and organized in your cloud content. - -With the Box Sign integration in Sim, you can: - -- **Create sign requests**: Send documents for e-signature with one or more signers, specifying roles (signer, approver, final copy reader) -- **Track signing status**: Monitor the progress of sign requests including who has signed and pending actions -- **List sign requests**: View all sign requests with marker-based pagination -- **Cancel requests**: Cancel pending sign requests that are no longer needed -- **Resend reminders**: Send reminder notifications to signers who haven't completed signing - -The Box Sign integration is ideal for automating document signing workflows such as offer letters, contracts, NDAs, and approval processes. Agents can create sign requests from documents already stored in Box, track completion, and trigger follow-up actions based on signing status. -{/* MANUAL-CONTENT-END */} - - -## Usage Instructions - -Integrate Box Sign into your workflow to send documents for e-signature. Create sign requests with multiple signers, track signing status, list all requests, cancel pending requests, and resend reminders. Ideal for offer letters, contracts, and other documents requiring signatures. - - - -## Tools - -### `box_sign_create_request` - -Create a new Box Sign request to send documents for e-signature - -#### Input - -| Parameter | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `sourceFileIds` | string | Yes | Comma-separated Box file IDs to send for signing | -| `signerEmail` | string | Yes | Primary signer email address | -| `signerRole` | string | No | Primary signer role: signer, approver, or final_copy_reader \(default: signer\) | -| `additionalSigners` | string | No | JSON array of additional signers, e.g. \[\{"email":"user@example.com","role":"signer"\}\] | -| `parentFolderId` | string | No | Box folder ID where signed documents will be stored \(default: user root\) | -| `emailSubject` | string | No | Custom subject line for the signing email | -| `emailMessage` | string | No | Custom message in the signing email body | -| `name` | string | No | Name for the sign request | -| `daysValid` | number | No | Number of days before the request expires \(0-730\) | -| `areRemindersEnabled` | boolean | No | Whether to send automatic signing reminders | -| `areTextSignaturesEnabled` | boolean | No | Whether to allow typed \(text\) signatures | -| `signatureColor` | string | No | Signature color: blue, black, or red | -| `redirectUrl` | string | No | URL to redirect signers to after signing | -| `declinedRedirectUrl` | string | No | URL to redirect signers to after declining | -| `isDocumentPreparationNeeded` | boolean | No | Whether document preparation is needed before sending | -| `externalId` | string | No | External system reference ID | - -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `id` | string | Sign request ID | -| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | -| `name` | string | Sign request name | -| `shortId` | string | Human-readable short ID | -| `signers` | array | List of signers | -| `sourceFiles` | array | Source files for signing | -| `emailSubject` | string | Custom email subject line | -| `emailMessage` | string | Custom email message body | -| `daysValid` | number | Number of days the request is valid | -| `createdAt` | string | Creation timestamp | -| `autoExpireAt` | string | Auto-expiration timestamp | -| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | -| `senderEmail` | string | Email of the sender | - -### `box_sign_get_request` - -Get the details and status of a Box Sign request - -#### Input - -| Parameter | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `signRequestId` | string | Yes | The ID of the sign request to retrieve | - -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `id` | string | Sign request ID | -| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | -| `name` | string | Sign request name | -| `shortId` | string | Human-readable short ID | -| `signers` | array | List of signers | -| `sourceFiles` | array | Source files for signing | -| `emailSubject` | string | Custom email subject line | -| `emailMessage` | string | Custom email message body | -| `daysValid` | number | Number of days the request is valid | -| `createdAt` | string | Creation timestamp | -| `autoExpireAt` | string | Auto-expiration timestamp | -| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | -| `senderEmail` | string | Email of the sender | - -### `box_sign_list_requests` - -List all Box Sign requests - -#### Input - -| Parameter | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `limit` | number | No | Maximum number of sign requests to return \(max 1000\) | -| `marker` | string | No | Pagination marker from a previous response | - -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `signRequests` | array | List of sign requests | -| ↳ `id` | string | Sign request ID | -| ↳ `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | -| ↳ `name` | string | Sign request name | -| ↳ `shortId` | string | Human-readable short ID | -| ↳ `signers` | array | List of signers | -| ↳ `sourceFiles` | array | Source files for signing | -| ↳ `emailSubject` | string | Custom email subject line | -| ↳ `emailMessage` | string | Custom email message body | -| ↳ `daysValid` | number | Number of days the request is valid | -| ↳ `createdAt` | string | Creation timestamp | -| ↳ `autoExpireAt` | string | Auto-expiration timestamp | -| ↳ `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | -| ↳ `senderEmail` | string | Email of the sender | -| `count` | number | Number of sign requests returned in this page | -| `nextMarker` | string | Marker for next page of results | - -### `box_sign_cancel_request` - -Cancel a pending Box Sign request - -#### Input - -| Parameter | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `signRequestId` | string | Yes | The ID of the sign request to cancel | - -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `id` | string | Sign request ID | -| `status` | string | Request status \(converting, created, sent, viewed, signed, cancelled, declined, expired, error_converting, error_sending, finalizing, error_finalizing\) | -| `name` | string | Sign request name | -| `shortId` | string | Human-readable short ID | -| `signers` | array | List of signers | -| `sourceFiles` | array | Source files for signing | -| `emailSubject` | string | Custom email subject line | -| `emailMessage` | string | Custom email message body | -| `daysValid` | number | Number of days the request is valid | -| `createdAt` | string | Creation timestamp | -| `autoExpireAt` | string | Auto-expiration timestamp | -| `prepareUrl` | string | URL for document preparation \(if preparation is needed\) | -| `senderEmail` | string | Email of the sender | - -### `box_sign_resend_request` - -Resend a Box Sign request to signers who have not yet signed - -#### Input - -| Parameter | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `signRequestId` | string | Yes | The ID of the sign request to resend | - -#### Output - -| Parameter | Type | Description | -| --------- | ---- | ----------- | -| `message` | string | Success confirmation message | - - diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 70548f0b052..faa946a2028 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -14,7 +14,6 @@ "ashby", "attio", "box", - "box_sign", "brandfetch", "browser_use", "calcom", @@ -170,4 +169,4 @@ "zep", "zoom" ] -} +} \ No newline at end of file diff --git a/apps/sim/blocks/blocks/box.ts b/apps/sim/blocks/blocks/box.ts index 7e2f0e004fc..5b1830da2dc 100644 --- a/apps/sim/blocks/blocks/box.ts +++ b/apps/sim/blocks/blocks/box.ts @@ -7,9 +7,9 @@ import { normalizeFileInput } from '@/blocks/utils' export const BoxBlock: BlockConfig = { type: 'box', name: 'Box', - description: 'Upload, download, search, and manage files and folders in Box', + description: 'Manage files, folders, and e-signatures with Box', longDescription: - 'Integrate Box into your workflow to manage files and folders. Upload and download files, get file information, list folder contents, create and delete folders, copy files, search across your Box account, and update file metadata.', + 'Integrate Box into your workflow to manage files, folders, and e-signatures. Upload and download files, search content, create folders, send documents for e-signature, track signing status, and more.', docsLink: 'https://docs.sim.ai/tools/box', category: 'tools', bgColor: '#0061D5', @@ -32,6 +32,11 @@ export const BoxBlock: BlockConfig = { { label: 'Copy File', id: 'copy_file' }, { label: 'Search', id: 'search' }, { label: 'Update File', id: 'update_file' }, + { label: 'Create Sign Request', id: 'sign_create_request' }, + { label: 'Get Sign Request', id: 'sign_get_request' }, + { label: 'List Sign Requests', id: 'sign_list_requests' }, + { label: 'Cancel Sign Request', id: 'sign_cancel_request' }, + { label: 'Resend Sign Request', id: 'sign_resend_request' }, ], value: () => 'upload_file', }, @@ -209,13 +214,16 @@ export const BoxBlock: BlockConfig = { condition: { field: 'operation', value: 'delete_folder' }, }, - // Shared pagination fields + // Shared pagination fields (file operations) { id: 'limit', title: 'Limit', type: 'short-input', placeholder: 'Max results per page', - condition: { field: 'operation', value: ['list_folder_items', 'search'] }, + condition: { + field: 'operation', + value: ['list_folder_items', 'search', 'sign_list_requests'], + }, mode: 'advanced', }, { @@ -255,6 +263,165 @@ export const BoxBlock: BlockConfig = { condition: { field: 'operation', value: 'list_folder_items' }, mode: 'advanced', }, + + // Sign Request fields + { + id: 'sourceFileIds', + title: 'Source File IDs', + type: 'short-input', + placeholder: 'Comma-separated Box file IDs (e.g., 12345,67890)', + required: { field: 'operation', value: 'sign_create_request' }, + condition: { field: 'operation', value: 'sign_create_request' }, + }, + { + id: 'signerEmail', + title: 'Signer Email', + type: 'short-input', + placeholder: 'Primary signer email address', + required: { field: 'operation', value: 'sign_create_request' }, + condition: { field: 'operation', value: 'sign_create_request' }, + }, + { + id: 'signerRole', + title: 'Signer Role', + type: 'dropdown', + options: [ + { label: 'Signer', id: 'signer' }, + { label: 'Approver', id: 'approver' }, + { label: 'Final Copy Reader', id: 'final_copy_reader' }, + ], + value: () => 'signer', + condition: { field: 'operation', value: 'sign_create_request' }, + }, + { + id: 'emailSubject', + title: 'Email Subject', + type: 'short-input', + placeholder: 'Custom email subject line', + condition: { field: 'operation', value: 'sign_create_request' }, + }, + { + id: 'emailMessage', + title: 'Email Message', + type: 'long-input', + placeholder: 'Custom message in the signing email', + condition: { field: 'operation', value: 'sign_create_request' }, + }, + { + id: 'signRequestName', + title: 'Request Name', + type: 'short-input', + placeholder: 'Name for this sign request', + condition: { field: 'operation', value: 'sign_create_request' }, + }, + { + id: 'additionalSigners', + title: 'Additional Signers', + type: 'long-input', + placeholder: '[{"email":"user@example.com","role":"signer"}]', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'signParentFolderId', + title: 'Destination Folder ID', + type: 'short-input', + placeholder: 'Box folder ID for signed documents', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'daysValid', + title: 'Days Valid', + type: 'short-input', + placeholder: 'Number of days before expiry (0-730)', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'areRemindersEnabled', + title: 'Enable Reminders', + type: 'switch', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'areTextSignaturesEnabled', + title: 'Allow Text Signatures', + type: 'switch', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'signatureColor', + title: 'Signature Color', + type: 'dropdown', + options: [ + { label: 'Blue', id: 'blue' }, + { label: 'Black', id: 'black' }, + { label: 'Red', id: 'red' }, + ], + value: () => 'blue', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'redirectUrl', + title: 'Redirect URL', + type: 'short-input', + placeholder: 'URL to redirect after signing', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'declinedRedirectUrl', + title: 'Declined Redirect URL', + type: 'short-input', + placeholder: 'URL to redirect after declining', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'isDocumentPreparationNeeded', + title: 'Document Preparation Needed', + type: 'switch', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + { + id: 'externalId', + title: 'External ID', + type: 'short-input', + placeholder: 'External system reference ID', + condition: { field: 'operation', value: 'sign_create_request' }, + mode: 'advanced', + }, + + // Sign Request ID (shared by get, cancel, resend) + { + id: 'signRequestId', + title: 'Sign Request ID', + type: 'short-input', + placeholder: 'Box Sign request ID', + required: { + field: 'operation', + value: ['sign_get_request', 'sign_cancel_request', 'sign_resend_request'], + }, + condition: { + field: 'operation', + value: ['sign_get_request', 'sign_cancel_request', 'sign_resend_request'], + }, + }, + + // Sign list pagination marker + { + id: 'marker', + title: 'Pagination Marker', + type: 'short-input', + placeholder: 'Marker from previous response', + condition: { field: 'operation', value: 'sign_list_requests' }, + mode: 'advanced', + }, ], tools: { @@ -269,9 +436,20 @@ export const BoxBlock: BlockConfig = { 'box_copy_file', 'box_search', 'box_update_file', + 'box_sign_create_request', + 'box_sign_get_request', + 'box_sign_list_requests', + 'box_sign_cancel_request', + 'box_sign_resend_request', ], config: { - tool: (params) => `box_${params.operation}`, + tool: (params) => { + const op = params.operation as string + if (op.startsWith('sign_')) { + return `box_${op}` + } + return `box_${op}` + }, params: (params) => { const normalizedFile = normalizeFileInput(params.file, { single: true }) if (normalizedFile) { @@ -329,6 +507,36 @@ export const BoxBlock: BlockConfig = { if (rest.moveToFolderId) baseParams.parentFolderId = rest.moveToFolderId if (rest.tags) baseParams.tags = rest.tags break + case 'sign_create_request': + baseParams.sourceFileIds = rest.sourceFileIds + baseParams.signerEmail = rest.signerEmail + if (rest.signerRole) baseParams.signerRole = rest.signerRole + if (rest.additionalSigners) baseParams.additionalSigners = rest.additionalSigners + if (rest.signParentFolderId) baseParams.parentFolderId = rest.signParentFolderId + if (rest.emailSubject) baseParams.emailSubject = rest.emailSubject + if (rest.emailMessage) baseParams.emailMessage = rest.emailMessage + if (rest.signRequestName) baseParams.name = rest.signRequestName + if (rest.daysValid) baseParams.daysValid = Number(rest.daysValid) + if (rest.areRemindersEnabled !== undefined) + baseParams.areRemindersEnabled = rest.areRemindersEnabled + if (rest.areTextSignaturesEnabled !== undefined) + baseParams.areTextSignaturesEnabled = rest.areTextSignaturesEnabled + if (rest.signatureColor) baseParams.signatureColor = rest.signatureColor + if (rest.redirectUrl) baseParams.redirectUrl = rest.redirectUrl + if (rest.declinedRedirectUrl) baseParams.declinedRedirectUrl = rest.declinedRedirectUrl + if (rest.isDocumentPreparationNeeded !== undefined) + baseParams.isDocumentPreparationNeeded = rest.isDocumentPreparationNeeded + if (rest.externalId) baseParams.externalId = rest.externalId + break + case 'sign_get_request': + case 'sign_cancel_request': + case 'sign_resend_request': + baseParams.signRequestId = rest.signRequestId + break + case 'sign_list_requests': + if (rest.limit) baseParams.limit = Number(rest.limit) + if (rest.marker) baseParams.marker = rest.marker + break } return baseParams @@ -344,6 +552,9 @@ export const BoxBlock: BlockConfig = { folderId: { type: 'string', description: 'Box folder ID' }, parentFolderId: { type: 'string', description: 'Parent folder ID' }, query: { type: 'string', description: 'Search query' }, + sourceFileIds: { type: 'string', description: 'Comma-separated Box file IDs' }, + signerEmail: { type: 'string', description: 'Primary signer email address' }, + signRequestId: { type: 'string', description: 'Sign request ID' }, }, outputs: { @@ -371,5 +582,18 @@ export const BoxBlock: BlockConfig = { results: 'json', deleted: 'boolean', message: 'string', + status: 'string', + shortId: 'string', + signers: 'json', + sourceFiles: 'json', + emailSubject: 'string', + emailMessage: 'string', + daysValid: 'number', + autoExpireAt: 'string', + prepareUrl: 'string', + senderEmail: 'string', + signRequests: 'json', + count: 'number', + nextMarker: 'string', }, } diff --git a/apps/sim/blocks/blocks/box_sign.ts b/apps/sim/blocks/blocks/box_sign.ts deleted file mode 100644 index 0a6f7e95bc2..00000000000 --- a/apps/sim/blocks/blocks/box_sign.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { BoxCompanyIcon } from '@/components/icons' -import { getScopesForService } from '@/lib/oauth/utils' -import type { BlockConfig } from '@/blocks/types' -import { AuthMode } from '@/blocks/types' - -export const BoxSignBlock: BlockConfig = { - type: 'box_sign', - name: 'Box Sign', - description: - 'Send documents for e-signature, check status, and manage sign requests with Box Sign', - longDescription: - 'Integrate Box Sign into your workflow to send documents for e-signature. Create sign requests with multiple signers, track signing status, list all requests, cancel pending requests, and resend reminders. Ideal for offer letters, contracts, and other documents requiring signatures.', - docsLink: 'https://docs.sim.ai/tools/box_sign', - category: 'tools', - bgColor: '#0061D5', - icon: BoxCompanyIcon, - authMode: AuthMode.OAuth, - - subBlocks: [ - { - id: 'operation', - title: 'Operation', - type: 'dropdown', - options: [ - { label: 'Create Sign Request', id: 'create_request' }, - { label: 'Get Sign Request', id: 'get_request' }, - { label: 'List Sign Requests', id: 'list_requests' }, - { label: 'Cancel Sign Request', id: 'cancel_request' }, - { label: 'Resend Sign Request', id: 'resend_request' }, - ], - value: () => 'create_request', - }, - { - id: 'credential', - title: 'Box Account', - type: 'oauth-input', - serviceId: 'box', - requiredScopes: getScopesForService('box'), - placeholder: 'Select Box account', - required: true, - }, - - // Create Sign Request fields - { - id: 'sourceFileIds', - title: 'Source File IDs', - type: 'short-input', - placeholder: 'Comma-separated Box file IDs (e.g., 12345,67890)', - required: { field: 'operation', value: 'create_request' }, - condition: { field: 'operation', value: 'create_request' }, - }, - { - id: 'signerEmail', - title: 'Signer Email', - type: 'short-input', - placeholder: 'Primary signer email address', - required: { field: 'operation', value: 'create_request' }, - condition: { field: 'operation', value: 'create_request' }, - }, - { - id: 'signerRole', - title: 'Signer Role', - type: 'dropdown', - options: [ - { label: 'Signer', id: 'signer' }, - { label: 'Approver', id: 'approver' }, - { label: 'Final Copy Reader', id: 'final_copy_reader' }, - ], - value: () => 'signer', - condition: { field: 'operation', value: 'create_request' }, - }, - { - id: 'emailSubject', - title: 'Email Subject', - type: 'short-input', - placeholder: 'Custom email subject line', - condition: { field: 'operation', value: 'create_request' }, - }, - { - id: 'emailMessage', - title: 'Email Message', - type: 'long-input', - placeholder: 'Custom message in the signing email', - condition: { field: 'operation', value: 'create_request' }, - }, - { - id: 'name', - title: 'Request Name', - type: 'short-input', - placeholder: 'Name for this sign request', - condition: { field: 'operation', value: 'create_request' }, - }, - { - id: 'additionalSigners', - title: 'Additional Signers', - type: 'long-input', - placeholder: '[{"email":"user@example.com","role":"signer"}]', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'parentFolderId', - title: 'Destination Folder ID', - type: 'short-input', - placeholder: 'Box folder ID for signed documents', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'daysValid', - title: 'Days Valid', - type: 'short-input', - placeholder: 'Number of days before expiry (0-730)', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'areRemindersEnabled', - title: 'Enable Reminders', - type: 'switch', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'areTextSignaturesEnabled', - title: 'Allow Text Signatures', - type: 'switch', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'signatureColor', - title: 'Signature Color', - type: 'dropdown', - options: [ - { label: 'Blue', id: 'blue' }, - { label: 'Black', id: 'black' }, - { label: 'Red', id: 'red' }, - ], - value: () => 'blue', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'redirectUrl', - title: 'Redirect URL', - type: 'short-input', - placeholder: 'URL to redirect after signing', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'declinedRedirectUrl', - title: 'Declined Redirect URL', - type: 'short-input', - placeholder: 'URL to redirect after declining', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'isDocumentPreparationNeeded', - title: 'Document Preparation Needed', - type: 'switch', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - { - id: 'externalId', - title: 'External ID', - type: 'short-input', - placeholder: 'External system reference ID', - condition: { field: 'operation', value: 'create_request' }, - mode: 'advanced', - }, - - // Get / Cancel / Resend fields - { - id: 'signRequestId', - title: 'Sign Request ID', - type: 'short-input', - placeholder: 'Box Sign request ID', - required: { field: 'operation', value: ['get_request', 'cancel_request', 'resend_request'] }, - condition: { - field: 'operation', - value: ['get_request', 'cancel_request', 'resend_request'], - }, - }, - - // List fields - { - id: 'limit', - title: 'Limit', - type: 'short-input', - placeholder: 'Max results (default: 100, max: 1000)', - condition: { field: 'operation', value: 'list_requests' }, - mode: 'advanced', - }, - { - id: 'marker', - title: 'Pagination Marker', - type: 'short-input', - placeholder: 'Marker from previous response', - condition: { field: 'operation', value: 'list_requests' }, - mode: 'advanced', - }, - ], - - tools: { - access: [ - 'box_sign_create_request', - 'box_sign_get_request', - 'box_sign_list_requests', - 'box_sign_cancel_request', - 'box_sign_resend_request', - ], - config: { - tool: (params) => `box_sign_${params.operation}`, - params: (params) => { - const { credential, operation, ...rest } = params - - const baseParams: Record = { - accessToken: credential, - } - - switch (operation) { - case 'create_request': - baseParams.sourceFileIds = rest.sourceFileIds - baseParams.signerEmail = rest.signerEmail - if (rest.signerRole) baseParams.signerRole = rest.signerRole - if (rest.additionalSigners) baseParams.additionalSigners = rest.additionalSigners - if (rest.parentFolderId) baseParams.parentFolderId = rest.parentFolderId - if (rest.emailSubject) baseParams.emailSubject = rest.emailSubject - if (rest.emailMessage) baseParams.emailMessage = rest.emailMessage - if (rest.name) baseParams.name = rest.name - if (rest.daysValid) baseParams.daysValid = Number(rest.daysValid) - if (rest.areRemindersEnabled !== undefined) - baseParams.areRemindersEnabled = rest.areRemindersEnabled - if (rest.areTextSignaturesEnabled !== undefined) - baseParams.areTextSignaturesEnabled = rest.areTextSignaturesEnabled - if (rest.signatureColor) baseParams.signatureColor = rest.signatureColor - if (rest.redirectUrl) baseParams.redirectUrl = rest.redirectUrl - if (rest.declinedRedirectUrl) baseParams.declinedRedirectUrl = rest.declinedRedirectUrl - if (rest.isDocumentPreparationNeeded !== undefined) - baseParams.isDocumentPreparationNeeded = rest.isDocumentPreparationNeeded - if (rest.externalId) baseParams.externalId = rest.externalId - break - case 'get_request': - case 'resend_request': - baseParams.signRequestId = rest.signRequestId - break - case 'cancel_request': - baseParams.signRequestId = rest.signRequestId - break - case 'list_requests': - if (rest.limit) baseParams.limit = Number(rest.limit) - if (rest.marker) baseParams.marker = rest.marker - break - } - - return baseParams - }, - }, - }, - - inputs: { - operation: { type: 'string', description: 'Operation to perform' }, - credential: { type: 'string', description: 'Box OAuth credential' }, - sourceFileIds: { type: 'string', description: 'Comma-separated Box file IDs' }, - signerEmail: { type: 'string', description: 'Primary signer email address' }, - signRequestId: { type: 'string', description: 'Sign request ID' }, - }, - - outputs: { - id: 'string', - status: 'string', - name: 'string', - shortId: 'string', - signers: 'json', - sourceFiles: 'json', - emailSubject: 'string', - emailMessage: 'string', - daysValid: 'number', - createdAt: 'string', - autoExpireAt: 'string', - prepareUrl: 'string', - senderEmail: 'string', - signRequests: 'json', - count: 'number', - nextMarker: 'string', - message: 'string', - }, -} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 2e0802cc51d..bc97a078cb0 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -14,7 +14,6 @@ import { AsanaBlock } from '@/blocks/blocks/asana' import { AshbyBlock } from '@/blocks/blocks/ashby' import { AttioBlock } from '@/blocks/blocks/attio' import { BoxBlock } from '@/blocks/blocks/box' -import { BoxSignBlock } from '@/blocks/blocks/box_sign' import { BrandfetchBlock } from '@/blocks/blocks/brandfetch' import { BrowserUseBlock } from '@/blocks/blocks/browser_use' import { CalComBlock } from '@/blocks/blocks/calcom' @@ -219,7 +218,6 @@ export const registry: Record = { attio: AttioBlock, brandfetch: BrandfetchBlock, box: BoxBlock, - box_sign: BoxSignBlock, browser_use: BrowserUseBlock, calcom: CalComBlock, calendly: CalendlyBlock, From ea948a692ecd9d62957959c42d270bd45f5f652d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 21:58:59 -0700 Subject: [PATCH 09/16] fix(box): filter empty strings from tags array in update_file Co-Authored-By: Claude Opus 4.6 --- apps/sim/tools/box/update_file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/tools/box/update_file.ts b/apps/sim/tools/box/update_file.ts index f44446f9a1a..f3ecb615072 100644 --- a/apps/sim/tools/box/update_file.ts +++ b/apps/sim/tools/box/update_file.ts @@ -64,7 +64,7 @@ export const boxUpdateFileTool: ToolConfig t.trim()) + if (params.tags) body.tags = params.tags.split(',').map((t: string) => t.trim()).filter(Boolean) return body }, }, From faeb293ead4d90f48a8275c255f155919cf4cb2a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 22:01:17 -0700 Subject: [PATCH 10/16] style(docs): apply lint formatting to icon-mapping and meta.json Co-Authored-By: Claude Opus 4.6 --- apps/docs/components/ui/icon-mapping.ts | 12 ++++++------ apps/docs/content/docs/en/tools/meta.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index cb759febd5f..8d2be1cae95 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -49,8 +49,8 @@ import { FirecrawlIcon, FirefliesIcon, GammaIcon, - GitLabIcon, GithubIcon, + GitLabIcon, GmailIcon, GongIcon, GoogleAdsIcon, @@ -93,9 +93,9 @@ import { LinkupIcon, LoopsIcon, LumaIcon, - MailServerIcon, MailchimpIcon, MailgunIcon, + MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -130,8 +130,6 @@ import { ResendIcon, RevenueCatIcon, S3Icon, - SQSIcon, - STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -143,17 +141,19 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, + SQSIcon, SshIcon, + STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, - TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, + TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -164,11 +164,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, + xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, - xIcon, } from '@/components/icons' type IconComponent = ComponentType> diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index faa946a2028..b8df33eb269 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -169,4 +169,4 @@ "zep", "zoom" ] -} \ No newline at end of file +} From 2c0ed6bca8c5d17f1c3051ebb7839c8a974f09c9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 22:03:12 -0700 Subject: [PATCH 11/16] style(box): format chained method calls per linter rules Co-Authored-By: Claude Opus 4.6 --- apps/sim/tools/box/update_file.ts | 6 +++++- apps/sim/tools/box_sign/create_request.ts | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/box/update_file.ts b/apps/sim/tools/box/update_file.ts index f3ecb615072..ad285152fc1 100644 --- a/apps/sim/tools/box/update_file.ts +++ b/apps/sim/tools/box/update_file.ts @@ -64,7 +64,11 @@ export const boxUpdateFileTool: ToolConfig t.trim()).filter(Boolean) + if (params.tags) + body.tags = params.tags + .split(',') + .map((t: string) => t.trim()) + .filter(Boolean) return body }, }, diff --git a/apps/sim/tools/box_sign/create_request.ts b/apps/sim/tools/box_sign/create_request.ts index a79856ad134..93778fa5b03 100644 --- a/apps/sim/tools/box_sign/create_request.ts +++ b/apps/sim/tools/box_sign/create_request.ts @@ -127,7 +127,10 @@ export const boxSignCreateRequestTool: ToolConfig { - const fileIds = params.sourceFileIds.split(',').map((id: string) => id.trim()).filter(Boolean) + const fileIds = params.sourceFileIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) const sourceFiles = fileIds.map((id: string) => ({ type: 'file', id })) const signers: Array> = [ From 758f076d4814c04a56995cadc19b0f8c67eee198 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 22:20:34 -0700 Subject: [PATCH 12/16] style(box,docusign): set block bgColor to white and regenerate docs Co-Authored-By: Claude Opus 4.6 --- apps/docs/components/ui/icon-mapping.ts | 14 +- apps/docs/content/docs/en/tools/box.mdx | 2 +- apps/docs/content/docs/en/tools/docusign.mdx | 193 ++++++++++--------- apps/docs/content/docs/en/tools/meta.json | 2 +- apps/sim/blocks/blocks/box.ts | 2 +- apps/sim/blocks/blocks/docusign.ts | 2 +- 6 files changed, 108 insertions(+), 107 deletions(-) diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 8d2be1cae95..c01d7ca2f7d 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -32,8 +32,8 @@ import { DatadogIcon, DevinIcon, DiscordIcon, - DocumentIcon, DocuSignIcon, + DocumentIcon, DropboxIcon, DsPyIcon, DubIcon, @@ -49,8 +49,8 @@ import { FirecrawlIcon, FirefliesIcon, GammaIcon, - GithubIcon, GitLabIcon, + GithubIcon, GmailIcon, GongIcon, GoogleAdsIcon, @@ -93,9 +93,9 @@ import { LinkupIcon, LoopsIcon, LumaIcon, + MailServerIcon, MailchimpIcon, MailgunIcon, - MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -130,6 +130,8 @@ import { ResendIcon, RevenueCatIcon, S3Icon, + SQSIcon, + STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -141,19 +143,17 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, - SQSIcon, SshIcon, - STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, + TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, - TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -164,11 +164,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, - xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, + xIcon, } from '@/components/icons' type IconComponent = ComponentType> diff --git a/apps/docs/content/docs/en/tools/box.mdx b/apps/docs/content/docs/en/tools/box.mdx index eea5523df56..905de5cee1a 100644 --- a/apps/docs/content/docs/en/tools/box.mdx +++ b/apps/docs/content/docs/en/tools/box.mdx @@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" {/* MANUAL-CONTENT-START:intro */} diff --git a/apps/docs/content/docs/en/tools/docusign.mdx b/apps/docs/content/docs/en/tools/docusign.mdx index 45058d9c5f1..3930a121554 100644 --- a/apps/docs/content/docs/en/tools/docusign.mdx +++ b/apps/docs/content/docs/en/tools/docusign.mdx @@ -5,9 +5,9 @@ description: Send documents for e-signature via DocuSign import { BlockInfoCard } from "@/components/ui/block-info-card" - {/* MANUAL-CONTENT-START:intro */} @@ -26,6 +26,7 @@ With the DocuSign integration in Sim, you can: In Sim, the DocuSign integration enables your agents to automate document workflows end-to-end. Agents can generate agreements, send them for signature, monitor completion, and retrieve signed copies—powering contract management, HR onboarding, sales closings, and compliance processes. {/* MANUAL-CONTENT-END */} + ## Usage Instructions Create and send envelopes for e-signature, use templates, check signing status, download signed documents, and manage recipients with DocuSign. @@ -36,194 +37,194 @@ Create and send envelopes for e-signature, use templates, check signing status, ### `docusign_send_envelope` +Create and send a DocuSign envelope with a document for e-signature + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `emailSubject` | string | Yes | Email subject for the envelope | +| `emailBody` | string | No | Email body message | +| `signerEmail` | string | Yes | Email address of the signer | +| `signerName` | string | Yes | Full name of the signer | +| `ccEmail` | string | No | Email address of carbon copy recipient | +| `ccName` | string | No | Full name of carbon copy recipient | +| `file` | file | No | Document file to send for signature | +| `status` | string | No | Envelope status: "sent" to send immediately, "created" for draft \(default: "sent"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `envelopeId` | string | Created envelope ID | +| `status` | string | Envelope status | +| `statusDateTime` | string | Status change datetime | +| `uri` | string | Envelope URI | ### `docusign_create_from_template` +Create and send a DocuSign envelope using a pre-built template + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `templateId` | string | Yes | DocuSign template ID to use | +| `emailSubject` | string | No | Override email subject \(uses template default if not set\) | +| `emailBody` | string | No | Override email body message | +| `templateRoles` | string | Yes | JSON array of template roles, e.g. \[\{"roleName":"Signer","name":"John","email":"john@example.com"\}\] | +| `status` | string | No | Envelope status: "sent" to send immediately, "created" for draft \(default: "sent"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `envelopeId` | string | Created envelope ID | +| `status` | string | Envelope status | +| `statusDateTime` | string | Status change datetime | +| `uri` | string | Envelope URI | ### `docusign_get_envelope` +Get the details and status of a DocuSign envelope + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `envelopeId` | string | Yes | The envelope ID to retrieve | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `status` | string | Envelope status \(created, sent, delivered, completed, declined, voided\) | +| `emailSubject` | string | Email subject line | +| `sentDateTime` | string | When the envelope was sent | +| `completedDateTime` | string | When all recipients completed signing | +| `createdDateTime` | string | When the envelope was created | +| `statusChangedDateTime` | string | When the status last changed | +| `voidedReason` | string | Reason the envelope was voided | +| `signerCount` | number | Number of signers | +| `documentCount` | number | Number of documents | ### `docusign_list_envelopes` +List envelopes from your DocuSign account with optional filters + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `fromDate` | string | No | Start date filter \(ISO 8601\). Defaults to 30 days ago | +| `toDate` | string | No | End date filter \(ISO 8601\) | +| `envelopeStatus` | string | No | Filter by status: created, sent, delivered, completed, declined, voided | +| `searchText` | string | No | Search text to filter envelopes | +| `count` | string | No | Maximum number of envelopes to return \(default: 25\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `envelopes` | array | Array of DocuSign envelopes | +| ↳ `envelopeId` | string | Unique envelope identifier | +| ↳ `status` | string | Envelope status \(created, sent, delivered, completed, declined, voided\) | +| ↳ `emailSubject` | string | Email subject line | +| ↳ `sentDateTime` | string | ISO 8601 datetime when envelope was sent | +| ↳ `completedDateTime` | string | ISO 8601 datetime when envelope was completed | +| ↳ `createdDateTime` | string | ISO 8601 datetime when envelope was created | +| ↳ `statusChangedDateTime` | string | ISO 8601 datetime of last status change | +| `totalSetSize` | number | Total number of matching envelopes | +| `resultSetSize` | number | Number of envelopes returned in this response | ### `docusign_void_envelope` +Void (cancel) a sent DocuSign envelope that has not yet been completed + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `envelopeId` | string | Yes | The envelope ID to void | +| `voidedReason` | string | Yes | Reason for voiding the envelope | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `envelopeId` | string | Voided envelope ID | +| `status` | string | Envelope status \(voided\) | ### `docusign_download_document` +Download a signed document from a completed DocuSign envelope + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `envelopeId` | string | Yes | The envelope ID containing the document | +| `documentId` | string | No | Specific document ID to download, or "combined" for all documents merged \(default: "combined"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | | `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `mimeType` | string | MIME type of the document | +| `fileName` | string | Original file name | ### `docusign_list_templates` +List available templates in your DocuSign account + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `searchText` | string | No | Search text to filter templates by name | +| `count` | string | No | Maximum number of templates to return | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `templates` | array | Array of DocuSign templates | +| ↳ `templateId` | string | Template identifier | +| ↳ `name` | string | Template name | +| ↳ `description` | string | Template description | +| ↳ `shared` | boolean | Whether template is shared | +| ↳ `created` | string | ISO 8601 creation date | +| ↳ `lastModified` | string | ISO 8601 last modified date | +| `totalSetSize` | number | Total number of matching templates | +| `resultSetSize` | number | Number of templates returned in this response | ### `docusign_list_recipients` +Get the recipient status details for a DocuSign envelope + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `envelopeId` | string | Yes | The envelope ID to get recipients for | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `envelopeId` | string | Envelope ID | -| `status` | string | Envelope or operation status | -| `envelopes` | json | Array of envelopes | -| `templates` | json | Array of templates | -| `signers` | json | Array of signer recipients | -| `carbonCopies` | json | Array of CC recipients | -| `base64Content` | string | Base64-encoded document content | -| `mimeType` | string | Document MIME type | -| `fileName` | string | Document file name | -| `emailSubject` | string | Envelope email subject | -| `totalSetSize` | number | Total matching results | -| `resultSetSize` | number | Results returned | +| `signers` | array | Array of DocuSign recipients | +| ↳ `recipientId` | string | Recipient identifier | +| ↳ `name` | string | Recipient name | +| ↳ `email` | string | Recipient email address | +| ↳ `status` | string | Recipient signing status \(sent, delivered, completed, declined\) | +| ↳ `signedDateTime` | string | ISO 8601 datetime when recipient signed | +| ↳ `deliveredDateTime` | string | ISO 8601 datetime when delivered to recipient | +| `carbonCopies` | array | Array of carbon copy recipients | +| ↳ `recipientId` | string | Recipient ID | +| ↳ `name` | string | Recipient name | +| ↳ `email` | string | Recipient email | +| ↳ `status` | string | Recipient status | diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index b8df33eb269..faa946a2028 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -169,4 +169,4 @@ "zep", "zoom" ] -} +} \ No newline at end of file diff --git a/apps/sim/blocks/blocks/box.ts b/apps/sim/blocks/blocks/box.ts index 5b1830da2dc..a341b9e7cf6 100644 --- a/apps/sim/blocks/blocks/box.ts +++ b/apps/sim/blocks/blocks/box.ts @@ -12,7 +12,7 @@ export const BoxBlock: BlockConfig = { 'Integrate Box into your workflow to manage files, folders, and e-signatures. Upload and download files, search content, create folders, send documents for e-signature, track signing status, and more.', docsLink: 'https://docs.sim.ai/tools/box', category: 'tools', - bgColor: '#0061D5', + bgColor: '#FFFFFF', icon: BoxCompanyIcon, authMode: AuthMode.OAuth, diff --git a/apps/sim/blocks/blocks/docusign.ts b/apps/sim/blocks/blocks/docusign.ts index ec96524831a..3dd9cb11bff 100644 --- a/apps/sim/blocks/blocks/docusign.ts +++ b/apps/sim/blocks/blocks/docusign.ts @@ -13,7 +13,7 @@ export const DocuSignBlock: BlockConfig = { 'Create and send envelopes for e-signature, use templates, check signing status, download signed documents, and manage recipients with DocuSign.', docsLink: 'https://docs.sim.ai/tools/docusign', category: 'tools', - bgColor: '#4C00FF', + bgColor: '#FFFFFF', icon: DocuSignIcon, authMode: AuthMode.OAuth, From 17723bb44cb154145d97a7796bb5ae419db2da34 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 22:30:16 -0700 Subject: [PATCH 13/16] style(docs): apply lint formatting Co-Authored-By: Claude Opus 4.6 --- apps/docs/components/ui/icon-mapping.ts | 14 +++++++------- apps/docs/content/docs/en/tools/meta.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index c01d7ca2f7d..8d2be1cae95 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -32,8 +32,8 @@ import { DatadogIcon, DevinIcon, DiscordIcon, - DocuSignIcon, DocumentIcon, + DocuSignIcon, DropboxIcon, DsPyIcon, DubIcon, @@ -49,8 +49,8 @@ import { FirecrawlIcon, FirefliesIcon, GammaIcon, - GitLabIcon, GithubIcon, + GitLabIcon, GmailIcon, GongIcon, GoogleAdsIcon, @@ -93,9 +93,9 @@ import { LinkupIcon, LoopsIcon, LumaIcon, - MailServerIcon, MailchimpIcon, MailgunIcon, + MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -130,8 +130,6 @@ import { ResendIcon, RevenueCatIcon, S3Icon, - SQSIcon, - STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -143,17 +141,19 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, + SQSIcon, SshIcon, + STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, - TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, + TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -164,11 +164,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, + xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, - xIcon, } from '@/components/icons' type IconComponent = ComponentType> diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index faa946a2028..b8df33eb269 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -169,4 +169,4 @@ "zep", "zoom" ] -} \ No newline at end of file +} From 1e16fa975d452972260aa0101a34847be9f95179 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 22:52:47 -0700 Subject: [PATCH 14/16] fix(box): populate OAuth scopes for Box since token response omits them Box's OAuth2 token endpoint does not return a scope field in the response, so Better Auth stores nothing in the DB. This causes the credential selector to always show "Additional permissions required". Fix by populating the scope from the requested scopes in the account.create.before hook. Co-Authored-By: Claude Opus 4.6 --- apps/sim/lib/auth/auth.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 29d46e99db3..f84c054f4f9 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -211,6 +211,16 @@ export const auth = betterAuth({ modifiedAccount.refreshTokenExpiresAt = getMicrosoftRefreshTokenExpiry() } + // Box token response does not include a scope field, so Better Auth + // stores nothing. Populate it from the requested scopes so the + // credential-selector can verify permissions. + if (account.providerId === 'box' && !account.scope) { + const requestedScopes = getCanonicalScopesForProvider('box') + if (requestedScopes.length > 0) { + modifiedAccount.scope = requestedScopes.join(' ') + } + } + return { data: modifiedAccount } }, after: async (account) => { From 2da7a34e23e8df29845c1837f6e9e5c72fb27b97 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 18 Mar 2026 22:54:40 -0700 Subject: [PATCH 15/16] fix(box): add sign_requests.readwrite scope for Box Sign operations Box Sign API requires the sign_requests.readwrite scope in addition to root_readwrite. Without it, sign requests fail with "The request requires higher privileges than provided by the access token." Co-Authored-By: Claude Opus 4.6 --- apps/sim/lib/oauth/oauth.ts | 2 +- apps/sim/lib/oauth/utils.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 62a3fa95b49..44afa5dea15 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -583,7 +583,7 @@ export const OAUTH_PROVIDERS: Record = { providerId: 'box', icon: BoxCompanyIcon, baseProviderIcon: BoxCompanyIcon, - scopes: ['root_readwrite'], + scopes: ['root_readwrite', 'sign_requests.readwrite'], }, }, defaultService: 'box', diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index eca6af3a2f0..4672b4aa08a 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -342,6 +342,7 @@ export const SCOPE_DESCRIPTIONS: Record = { // Box scopes root_readwrite: 'Read and write all files and folders in Box account', root_readonly: 'Read all files and folders in Box account', + 'sign_requests.readwrite': 'Create and manage Box Sign e-signature requests', // Shopify scopes write_products: 'Read and manage Shopify products', From 39d79a055e67d38c88d3559a17f6f7907f4f4ed7 Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 18 Mar 2026 23:04:18 -0700 Subject: [PATCH 16/16] update docs --- apps/docs/components/icons.tsx | 16 ++++--- apps/docs/content/docs/en/tools/firecrawl.mdx | 17 +++++++- .../content/docs/en/tools/google_books.mdx | 4 ++ .../content/docs/en/tools/google_maps.mdx | 24 +++++++++++ .../docs/en/tools/google_pagespeed.mdx | 2 + .../docs/en/tools/google_translate.mdx | 6 +++ apps/docs/content/docs/en/tools/grain.mdx | 42 ++++++++++++------- apps/docs/content/docs/en/tools/jina.mdx | 2 + apps/docs/content/docs/en/tools/knowledge.mdx | 31 ++++++++++++++ apps/docs/content/docs/en/tools/linkup.mdx | 3 ++ .../docs/content/docs/en/tools/perplexity.mdx | 5 +++ apps/docs/content/docs/en/tools/serper.mdx | 3 ++ apps/sim/app/api/tools/docusign/route.ts | 2 +- apps/sim/components/icons.tsx | 16 ++++--- apps/sim/lib/auth/auth.ts | 8 ++-- apps/sim/lib/oauth/oauth.ts | 2 +- 16 files changed, 150 insertions(+), 33 deletions(-) diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 32b0bd2a173..f8ba78c4ba7 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4588,11 +4588,17 @@ export function ShopifyIcon(props: SVGProps) { export function BoxCompanyIcon(props: SVGProps) { return ( - - + + + + + ) } diff --git a/apps/docs/content/docs/en/tools/firecrawl.mdx b/apps/docs/content/docs/en/tools/firecrawl.mdx index bbcc1d01f56..ee346b8e464 100644 --- a/apps/docs/content/docs/en/tools/firecrawl.mdx +++ b/apps/docs/content/docs/en/tools/firecrawl.mdx @@ -53,6 +53,9 @@ Extract structured content from web pages with comprehensive metadata support. C | `url` | string | Yes | The URL to scrape content from \(e.g., "https://example.com/page"\) | | `scrapeOptions` | json | No | Options for content scraping | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -86,6 +89,9 @@ Search for information on the web using Firecrawl | --------- | ---- | -------- | ----------- | | `query` | string | Yes | The search query to use | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -123,6 +129,9 @@ Crawl entire websites and extract structured content from all accessible pages | `includePaths` | json | No | URL paths to include in crawling \(e.g., \["/docs/*", "/api/*"\]\). Only these paths will be crawled | | `onlyMainContent` | boolean | No | Extract only main content from pages | | `apiKey` | string | Yes | Firecrawl API Key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -142,7 +151,6 @@ Crawl entire websites and extract structured content from all accessible pages | ↳ `statusCode` | number | HTTP status code | | ↳ `ogLocaleAlternate` | array | Alternate locale versions | | `total` | number | Total number of pages found during crawl | -| `creditsUsed` | number | Number of credits consumed by the crawl operation | ### `firecrawl_map` @@ -161,6 +169,9 @@ Get a complete list of URLs from any website quickly and reliably. Useful for di | `timeout` | number | No | Request timeout in milliseconds | | `location` | json | No | Geographic context for proxying \(country, languages\) | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -187,6 +198,9 @@ Extract structured data from entire webpages using natural language prompts and | `ignoreInvalidURLs` | boolean | No | Skip invalid URLs in the array \(default: true\) | | `scrapeOptions` | json | No | Advanced scraping configuration options | | `apiKey` | string | Yes | Firecrawl API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -217,7 +231,6 @@ Autonomous web data extraction agent. Searches and gathers information based on | `success` | boolean | Whether the agent operation was successful | | `status` | string | Current status of the agent job \(processing, completed, failed\) | | `data` | object | Extracted data from the agent | -| `creditsUsed` | number | Number of credits consumed by this agent task | | `expiresAt` | string | Timestamp when the results expire \(24 hours\) | | `sources` | object | Array of source URLs used by the agent | diff --git a/apps/docs/content/docs/en/tools/google_books.mdx b/apps/docs/content/docs/en/tools/google_books.mdx index 543dd6f1252..6ab604483d2 100644 --- a/apps/docs/content/docs/en/tools/google_books.mdx +++ b/apps/docs/content/docs/en/tools/google_books.mdx @@ -46,6 +46,8 @@ Search for books using the Google Books API | `startIndex` | number | No | Index of the first result to return \(for pagination\) | | `maxResults` | number | No | Maximum number of results to return \(1-40\) | | `langRestrict` | string | No | Restrict results to a specific language \(ISO 639-1 code\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -82,6 +84,8 @@ Get detailed information about a specific book volume | `apiKey` | string | Yes | Google Books API key | | `volumeId` | string | Yes | The ID of the volume to retrieve | | `projection` | string | No | Projection level \(full, lite\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/google_maps.mdx b/apps/docs/content/docs/en/tools/google_maps.mdx index 459ffa74312..5439d99cbf1 100644 --- a/apps/docs/content/docs/en/tools/google_maps.mdx +++ b/apps/docs/content/docs/en/tools/google_maps.mdx @@ -50,6 +50,8 @@ Get current air quality data for a location | `lat` | number | Yes | Latitude coordinate | | `lng` | number | Yes | Longitude coordinate | | `languageCode` | string | No | Language code for the response \(e.g., "en", "es"\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -91,6 +93,8 @@ Get directions and route information between two locations | `waypoints` | json | No | Array of intermediate waypoints | | `units` | string | No | Unit system: metric or imperial | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -135,6 +139,8 @@ Calculate travel distance and time between multiple origins and destinations | `avoid` | string | No | Features to avoid: tolls, highways, or ferries | | `units` | string | No | Unit system: metric or imperial | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -163,6 +169,8 @@ Get elevation data for a location | `apiKey` | string | Yes | Google Maps API key | | `lat` | number | Yes | Latitude coordinate | | `lng` | number | Yes | Longitude coordinate | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -185,6 +193,8 @@ Convert an address into geographic coordinates (latitude and longitude) | `address` | string | Yes | The address to geocode | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | | `region` | string | No | Region bias as a ccTLD code \(e.g., us, uk\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -217,6 +227,8 @@ Geolocate a device using WiFi access points, cell towers, or IP address | `considerIp` | boolean | No | Whether to use IP address for geolocation \(default: true\) | | `cellTowers` | array | No | Array of cell tower objects with cellId, locationAreaCode, mobileCountryCode, mobileNetworkCode | | `wifiAccessPoints` | array | No | Array of WiFi access point objects with macAddress \(required\), signalStrength, etc. | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -238,6 +250,8 @@ Get detailed information about a specific place | `placeId` | string | Yes | Google Place ID | | `fields` | string | No | Comma-separated list of fields to return | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -290,6 +304,8 @@ Search for places using a text query | `type` | string | No | Place type filter \(e.g., restaurant, cafe, hotel\) | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | | `region` | string | No | Region bias as a ccTLD code \(e.g., us, uk\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -322,6 +338,8 @@ Convert geographic coordinates (latitude and longitude) into a human-readable ad | `lat` | number | Yes | Latitude coordinate | | `lng` | number | Yes | Longitude coordinate | | `language` | string | No | Language code for results \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -346,6 +364,8 @@ Snap GPS coordinates to the nearest road segment | `apiKey` | string | Yes | Google Maps API key with Roads API enabled | | `path` | string | Yes | Pipe-separated list of lat,lng coordinates \(e.g., "60.170880,24.942795\|60.170879,24.942796"\) | | `interpolate` | boolean | No | Whether to interpolate additional points along the road | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -399,6 +419,8 @@ Get timezone information for a location | `lng` | number | Yes | Longitude coordinate | | `timestamp` | number | No | Unix timestamp to determine DST offset \(defaults to current time\) | | `language` | string | No | Language code for timezone name \(e.g., en, es, fr\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -424,6 +446,8 @@ Validate and standardize a postal address | `regionCode` | string | No | ISO 3166-1 alpha-2 country code \(e.g., "US", "CA"\) | | `locality` | string | No | City or locality name | | `enableUspsCass` | boolean | No | Enable USPS CASS validation for US addresses | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/google_pagespeed.mdx b/apps/docs/content/docs/en/tools/google_pagespeed.mdx index 65b62e0e750..d2cd247c851 100644 --- a/apps/docs/content/docs/en/tools/google_pagespeed.mdx +++ b/apps/docs/content/docs/en/tools/google_pagespeed.mdx @@ -55,6 +55,8 @@ Analyze a webpage for performance, accessibility, SEO, and best practices using | `category` | string | No | Lighthouse categories to analyze \(comma-separated\): performance, accessibility, best-practices, seo | | `strategy` | string | No | Analysis strategy: desktop or mobile | | `locale` | string | No | Locale for results \(e.g., en, fr, de\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/google_translate.mdx b/apps/docs/content/docs/en/tools/google_translate.mdx index 8c0bd6bebb7..a3b835f11be 100644 --- a/apps/docs/content/docs/en/tools/google_translate.mdx +++ b/apps/docs/content/docs/en/tools/google_translate.mdx @@ -43,6 +43,9 @@ Translate text between languages using the Google Cloud Translation API. Support | `target` | string | Yes | Target language code \(e.g., "es", "fr", "de", "ja"\) | | `source` | string | No | Source language code. If omitted, the API will auto-detect the source language. | | `format` | string | No | Format of the text: "text" for plain text, "html" for HTML content | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -61,6 +64,9 @@ Detect the language of text using the Google Cloud Translation API. | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Google Cloud API key with Cloud Translation API enabled | | `text` | string | Yes | The text to detect the language of | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/grain.mdx b/apps/docs/content/docs/en/tools/grain.mdx index 49e77f09a78..f0376177c59 100644 --- a/apps/docs/content/docs/en/tools/grain.mdx +++ b/apps/docs/content/docs/en/tools/grain.mdx @@ -138,6 +138,26 @@ Get the full transcript of a recording | ↳ `end` | number | End timestamp in ms | | ↳ `text` | string | Transcript text | +### `grain_list_views` + +List available Grain views for webhook subscriptions + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Grain API key \(Personal Access Token\) | +| `typeFilter` | string | No | Optional view type filter: recordings, highlights, or stories | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `views` | array | Array of Grain views | +| ↳ `id` | string | View UUID | +| ↳ `name` | string | View name | +| ↳ `type` | string | View type: recordings, highlights, or stories | + ### `grain_list_teams` List all teams in the workspace @@ -185,15 +205,9 @@ Create a webhook to receive recording events | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | Grain API key \(Personal Access Token\) | | `hookUrl` | string | Yes | Webhook endpoint URL \(e.g., "https://example.com/webhooks/grain"\) | -| `hookType` | string | Yes | Type of webhook: "recording_added" or "upload_status" | -| `filterBeforeDatetime` | string | No | Filter: recordings before this ISO8601 date \(e.g., "2024-01-15T00:00:00Z"\) | -| `filterAfterDatetime` | string | No | Filter: recordings after this ISO8601 date \(e.g., "2024-01-01T00:00:00Z"\) | -| `filterParticipantScope` | string | No | Filter: "internal" or "external" | -| `filterTeamId` | string | No | Filter: specific team UUID \(e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"\) | -| `filterMeetingTypeId` | string | No | Filter: specific meeting type UUID \(e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"\) | -| `includeHighlights` | boolean | No | Include highlights in webhook payload | -| `includeParticipants` | boolean | No | Include participants in webhook payload | -| `includeAiSummary` | boolean | No | Include AI summary in webhook payload | +| `viewId` | string | Yes | Grain view ID from GET /_/public-api/views | +| `actions` | array | No | Optional list of actions to subscribe to: added, updated, removed | +| `items` | string | No | No description | #### Output @@ -202,9 +216,8 @@ Create a webhook to receive recording events | `id` | string | Hook UUID | | `enabled` | boolean | Whether hook is active | | `hook_url` | string | The webhook URL | -| `hook_type` | string | Type of hook: recording_added or upload_status | -| `filter` | object | Applied filters | -| `include` | object | Included fields | +| `view_id` | string | Grain view ID for the webhook | +| `actions` | array | Configured actions for the webhook | | `inserted_at` | string | ISO8601 creation timestamp | ### `grain_list_hooks` @@ -225,9 +238,8 @@ List all webhooks for the account | ↳ `id` | string | Hook UUID | | ↳ `enabled` | boolean | Whether hook is active | | ↳ `hook_url` | string | Webhook URL | -| ↳ `hook_type` | string | Type: recording_added or upload_status | -| ↳ `filter` | object | Applied filters | -| ↳ `include` | object | Included fields | +| ↳ `view_id` | string | Grain view ID | +| ↳ `actions` | array | Configured actions | | ↳ `inserted_at` | string | Creation timestamp | ### `grain_delete_hook` diff --git a/apps/docs/content/docs/en/tools/jina.mdx b/apps/docs/content/docs/en/tools/jina.mdx index 9a2561c0da0..d04cf7d71a6 100644 --- a/apps/docs/content/docs/en/tools/jina.mdx +++ b/apps/docs/content/docs/en/tools/jina.mdx @@ -64,6 +64,7 @@ Extract and process web content into clean, LLM-friendly text using Jina AI Read | Parameter | Type | Description | | --------- | ---- | ----------- | | `content` | string | The extracted content from the URL, processed into clean, LLM-friendly text | +| `tokensUsed` | number | Number of Jina tokens consumed by this request | ### `jina_search` @@ -97,5 +98,6 @@ Search the web and return top 5 results with LLM-friendly content. Each result i | ↳ `content` | string | LLM-friendly extracted content | | ↳ `usage` | object | Token usage information | | ↳ `tokens` | number | Number of tokens consumed by this request | +| `tokensUsed` | number | Number of Jina tokens consumed by this request | diff --git a/apps/docs/content/docs/en/tools/knowledge.mdx b/apps/docs/content/docs/en/tools/knowledge.mdx index c198c304b2c..975754ed556 100644 --- a/apps/docs/content/docs/en/tools/knowledge.mdx +++ b/apps/docs/content/docs/en/tools/knowledge.mdx @@ -122,6 +122,37 @@ Create a new document in a knowledge base | `message` | string | Success or error message describing the operation result | | `documentId` | string | ID of the created document | +### `knowledge_upsert_document` + +Create or update a document in a knowledge base. If a document with the given ID or filename already exists, it will be replaced with the new content. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `knowledgeBaseId` | string | Yes | ID of the knowledge base containing the document | +| `documentId` | string | No | Optional ID of an existing document to update. If not provided, lookup is done by filename. | +| `name` | string | Yes | Name of the document | +| `content` | string | Yes | Content of the document | +| `documentTags` | json | No | Document tags | +| `documentTags` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `data` | object | Information about the upserted document | +| ↳ `documentId` | string | Document ID | +| ↳ `documentName` | string | Document name | +| ↳ `type` | string | Document type | +| ↳ `enabled` | boolean | Whether the document is enabled | +| ↳ `isUpdate` | boolean | Whether an existing document was replaced | +| ↳ `previousDocumentId` | string | ID of the document that was replaced, if any | +| ↳ `createdAt` | string | Creation timestamp | +| ↳ `updatedAt` | string | Last update timestamp | +| `message` | string | Success or error message describing the operation result | +| `documentId` | string | ID of the upserted document | + ### `knowledge_list_tags` List all tag definitions for a knowledge base diff --git a/apps/docs/content/docs/en/tools/linkup.mdx b/apps/docs/content/docs/en/tools/linkup.mdx index c3812f1d5be..62166b35426 100644 --- a/apps/docs/content/docs/en/tools/linkup.mdx +++ b/apps/docs/content/docs/en/tools/linkup.mdx @@ -51,6 +51,9 @@ Search the web for information using Linkup | `includeDomains` | string | No | Comma-separated list of domain names to restrict search results to | | `includeInlineCitations` | boolean | No | Add inline citations to answers \(only applies when outputType is "sourcedAnswer"\) | | `includeSources` | boolean | No | Include sources in response | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/perplexity.mdx b/apps/docs/content/docs/en/tools/perplexity.mdx index 75efe7a487f..1c98cb791eb 100644 --- a/apps/docs/content/docs/en/tools/perplexity.mdx +++ b/apps/docs/content/docs/en/tools/perplexity.mdx @@ -49,6 +49,9 @@ Generate completions using Perplexity AI chat models | `max_tokens` | number | No | Maximum number of tokens to generate \(e.g., 1024, 2048, 4096\) | | `temperature` | number | No | Sampling temperature between 0 and 1 \(e.g., 0.0 for deterministic, 0.7 for creative\) | | `apiKey` | string | Yes | Perplexity API key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output @@ -78,6 +81,8 @@ Get ranked search results from Perplexity | `search_after_date` | string | No | Include only content published after this date \(format: MM/DD/YYYY\) | | `search_before_date` | string | No | Include only content published before this date \(format: MM/DD/YYYY\) | | `apiKey` | string | Yes | Perplexity API key | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/docs/content/docs/en/tools/serper.mdx b/apps/docs/content/docs/en/tools/serper.mdx index ccc21f34e68..f025f0e453e 100644 --- a/apps/docs/content/docs/en/tools/serper.mdx +++ b/apps/docs/content/docs/en/tools/serper.mdx @@ -47,6 +47,9 @@ A powerful web search tool that provides access to Google search results through | `hl` | string | No | Language code for search results \(e.g., "en", "es", "de", "fr"\) | | `type` | string | No | Type of search to perform \(e.g., "search", "news", "images", "videos", "places", "shopping"\) | | `apiKey` | string | Yes | Serper API Key | +| `pricing` | custom | No | No description | +| `metadata` | string | No | No description | +| `rateLimit` | string | No | No description | #### Output diff --git a/apps/sim/app/api/tools/docusign/route.ts b/apps/sim/app/api/tools/docusign/route.ts index 0eef6e8a8cd..a808bfe0fdc 100644 --- a/apps/sim/app/api/tools/docusign/route.ts +++ b/apps/sim/app/api/tools/docusign/route.ts @@ -17,7 +17,7 @@ interface DocuSignAccountInfo { * by calling the DocuSign userinfo endpoint. */ async function resolveAccount(accessToken: string): Promise { - const response = await fetch('https://account.docusign.com/oauth/userinfo', { + const response = await fetch('https://account-d.docusign.com/oauth/userinfo', { headers: { Authorization: `Bearer ${accessToken}` }, }) diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 32b0bd2a173..f8ba78c4ba7 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4588,11 +4588,17 @@ export function ShopifyIcon(props: SVGProps) { export function BoxCompanyIcon(props: SVGProps) { return ( - - + + + + + ) } diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index f84c054f4f9..2795c09e378 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -2655,9 +2655,9 @@ export const auth = betterAuth({ providerId: 'docusign', clientId: env.DOCUSIGN_CLIENT_ID as string, clientSecret: env.DOCUSIGN_CLIENT_SECRET as string, - authorizationUrl: 'https://account.docusign.com/oauth/auth', - tokenUrl: 'https://account.docusign.com/oauth/token', - userInfoUrl: 'https://account.docusign.com/oauth/userinfo', + authorizationUrl: 'https://account-d.docusign.com/oauth/auth', + tokenUrl: 'https://account-d.docusign.com/oauth/token', + userInfoUrl: 'https://account-d.docusign.com/oauth/userinfo', scopes: getCanonicalScopesForProvider('docusign'), responseType: 'code', accessType: 'offline', @@ -2667,7 +2667,7 @@ export const auth = betterAuth({ try { logger.info('Fetching DocuSign user profile') - const response = await fetch('https://account.docusign.com/oauth/userinfo', { + const response = await fetch('https://account-d.docusign.com/oauth/userinfo', { headers: { Authorization: `Bearer ${tokens.accessToken}`, }, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 44afa5dea15..7328473e850 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -1156,7 +1156,7 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { env.DOCUSIGN_CLIENT_SECRET ) return { - tokenEndpoint: 'https://account.docusign.com/oauth/token', + tokenEndpoint: 'https://account-d.docusign.com/oauth/token', clientId, clientSecret, useBasicAuth: true,