feat(box): add Box and Box Sign integrations (#3660)

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* fix(box): filter empty strings from tags array in update_file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(docs): apply lint formatting to icon-mapping and meta.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(box): format chained method calls per linter rules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(box,docusign): set block bgColor to white and regenerate docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(docs): apply lint formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* update docs

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-03-18 23:06:08 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 1809b3801b
commit cef321bda2
47 changed files with 3374 additions and 131 deletions
+12 -6
View File
@@ -1409,7 +1409,7 @@ export function AmplitudeIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 49 49'>
<path
fill='#FFFFFF'
fill='currentColor'
d='M23.4,15.3c0.6,1.8,1.2,4.1,1.9,6.7c-2.6,0-5.3-0.1-7.8-0.1h-1.3c1.5-5.7,3.2-10.1,4.6-11.1 c0.1-0.1,0.2-0.1,0.4-0.1c0.2,0,0.3,0.1,0.5,0.3C21.9,11.5,22.5,12.7,23.4,15.3z M49,24.5C49,38,38,49,24.5,49S0,38,0,24.5 S11,0,24.5,0S49,11,49,24.5z M42.7,23.9c0-0.6-0.4-1.2-1-1.3l0,0l0,0l0,0c-0.1,0-0.1,0-0.2,0h-0.2c-4.1-0.3-8.4-0.4-12.4-0.5l0,0 C27,14.8,24.5,7.4,21.3,7.4c-3,0-5.8,4.9-8.2,14.5c-1.7,0-3.2,0-4.6-0.1c-0.1,0-0.2,0-0.2,0c-0.3,0-0.5,0-0.5,0 c-0.8,0.1-1.4,0.9-1.4,1.7c0,0.8,0.6,1.6,1.5,1.7l0,0h4.6c-0.4,1.9-0.8,3.8-1.1,5.6l-0.1,0.8l0,0c0,0.6,0.5,1.1,1.1,1.1 c0.4,0,0.8-0.2,1-0.5l0,0l2.2-7.1h10.7c0.8,3.1,1.7,6.3,2.8,9.3c0.6,1.6,2,5.4,4.4,5.4l0,0c3.6,0,5-5.8,5.9-9.6 c0.2-0.8,0.4-1.5,0.5-2.1l0.1-0.2l0,0c0-0.1,0-0.2,0-0.3c-0.1-0.2-0.2-0.3-0.4-0.4c-0.3-0.1-0.5,0.1-0.6,0.4l0,0l-0.1,0.2 c-0.3,0.8-0.6,1.6-0.8,2.3v0.1c-1.6,4.4-2.3,6.4-3.7,6.4l0,0l0,0l0,0c-1.8,0-3.5-7.3-4.1-10.1c-0.1-0.5-0.2-0.9-0.3-1.3h11.7 c0.2,0,0.4-0.1,0.6-0.1l0,0c0,0,0,0,0.1,0c0,0,0,0,0.1,0l0,0c0,0,0.1,0,0.1-0.1l0,0C42.5,24.6,42.7,24.3,42.7,23.9z'
/>
</svg>
@@ -4588,11 +4588,17 @@ export function ShopifyIcon(props: SVGProps<SVGSVGElement>) {
export function BoxCompanyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 41 22'>
<path
d='M39.7 19.2c.5.7.4 1.6-.2 2.1-.7.5-1.7.4-2.2-.2l-3.5-4.5-3.4 4.4c-.5.7-1.5.7-2.2.2-.7-.5-.8-1.4-.3-2.1l4-5.2-4-5.2c-.5-.7-.3-1.7.3-2.2.7-.5 1.7-.3 2.2.3l3.4 4.5L37.3 7c.5-.7 1.4-.8 2.2-.3.7.5.7 1.5.2 2.2L35.8 14l3.9 5.2zm-18.2-.6c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c-.1 2.6-2.2 4.6-4.7 4.6zm-13.8 0c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c0 2.6-2.1 4.6-4.7 4.6zM21.5 6.4c-2.9 0-5.5 1.6-6.8 4-1.3-2.4-3.9-4-6.9-4-1.8 0-3.4.6-4.7 1.5V1.5C3.1.7 2.4 0 1.6 0 .7 0 0 .7 0 1.5v12.6c.1 4.2 3.5 7.5 7.7 7.5 3 0 5.6-1.7 6.9-4.1 1.3 2.4 3.9 4.1 6.8 4.1 4.3 0 7.8-3.4 7.8-7.7.1-4.1-3.4-7.5-7.7-7.5z'
fill='currentColor'
/>
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
width='2500'
height='1379'
viewBox='0 0 444.893 245.414'
>
<g fill='#0075C9'>
<path d='M239.038 72.43c-33.081 0-61.806 18.6-76.322 45.904-14.516-27.305-43.24-45.902-76.32-45.902-19.443 0-37.385 6.424-51.821 17.266V16.925h-.008C34.365 7.547 26.713 0 17.286 0 7.858 0 .208 7.547.008 16.925H0v143.333h.036c.768 47.051 39.125 84.967 86.359 84.967 33.08 0 61.805-18.603 76.32-45.908 14.517 27.307 43.241 45.906 76.321 45.906 47.715 0 86.396-38.684 86.396-86.396.001-47.718-38.682-86.397-86.394-86.397zM86.395 210.648c-28.621 0-51.821-23.201-51.821-51.82 0-28.623 23.201-51.823 51.821-51.823 28.621 0 51.822 23.2 51.822 51.823 0 28.619-23.201 51.82-51.822 51.82zm152.643 0c-28.622 0-51.821-23.201-51.821-51.822 0-28.623 23.2-51.821 51.821-51.821 28.619 0 51.822 23.198 51.822 51.821-.001 28.621-23.203 51.822-51.822 51.822z' />
<path d='M441.651 218.033l-44.246-59.143 44.246-59.144-.008-.007c5.473-7.62 3.887-18.249-3.652-23.913-7.537-5.658-18.187-4.221-23.98 3.157l-.004-.002-38.188 51.047-38.188-51.047-.006.009c-5.793-7.385-16.441-8.822-23.981-3.16-7.539 5.664-9.125 16.293-3.649 23.911l-.008.005 44.245 59.144-44.245 59.143.008.005c-5.477 7.62-3.89 18.247 3.649 23.909 7.54 5.664 18.188 4.225 23.981-3.155l.006.007 38.188-51.049 38.188 51.049.004-.002c5.794 7.377 16.443 8.814 23.98 3.154 7.539-5.662 9.125-16.291 3.652-23.91l.008-.008z' />
</g>
</svg>
)
}
+2
View File
@@ -16,6 +16,7 @@ import {
AsanaIcon,
AshbyIcon,
AttioIcon,
BoxCompanyIcon,
BrainIcon,
BrandfetchIcon,
BrowserUseIcon,
@@ -185,6 +186,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
asana: AsanaIcon,
ashby: AshbyIcon,
attio: AttioIcon,
box: BoxCompanyIcon,
brandfetch: BrandfetchIcon,
browser_use: BrowserUseIcon,
calcom: CalComIcon,
+440
View File
@@ -0,0 +1,440 @@
---
title: Box
description: Manage files, folders, and e-signatures with Box
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="box"
color="#FFFFFF"
/>
{/* 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, including [Box Sign](https://www.box.com/esignature) for native e-signatures.
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
- **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, 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, folders, and e-signatures. Upload and download files, search content, create folders, send documents for e-signature, track signing status, and more.
## 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 | File ID |
| `name` | string | 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 |
| --------- | ---- | ----------- |
| `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 |
### `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 | File ID |
| `name` | string | 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 |
### `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 |
+97 -96
View File
@@ -5,9 +5,9 @@ description: Send documents for e-signature via DocuSign
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
<BlockInfoCard
type="docusign"
color="#4C00FF"
color="#FFFFFF"
/>
{/* 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 |
+15 -2
View File
@@ -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 |
@@ -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
@@ -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
@@ -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
@@ -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
+27 -15
View File
@@ -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`
+2
View File
@@ -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 |
@@ -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
@@ -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
@@ -13,6 +13,7 @@
"asana",
"ashby",
"attio",
"box",
"brandfetch",
"browser_use",
"calcom",
@@ -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
@@ -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
+140
View File
@@ -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 }
)
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ interface DocuSignAccountInfo {
* by calling the DocuSign userinfo endpoint.
*/
async function resolveAccount(accessToken: string): Promise<DocuSignAccountInfo> {
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}` },
})
+599
View File
@@ -0,0 +1,599 @@
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: 'Manage files, folders, and e-signatures with Box',
longDescription:
'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: '#FFFFFF',
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' },
{ 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',
},
{
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 (file operations)
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: 'Max results per page',
condition: {
field: 'operation',
value: ['list_folder_items', 'search', 'sign_list_requests'],
},
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',
},
// 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: {
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',
'box_sign_create_request',
'box_sign_get_request',
'box_sign_list_requests',
'box_sign_cancel_request',
'box_sign_resend_request',
],
config: {
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) {
params.file = normalizedFile
}
const { credential, operation, ...rest } = params
const baseParams: Record<string, unknown> = {
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
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
},
},
},
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' },
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',
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',
entries: 'json',
totalCount: 'number',
offset: 'number',
limit: 'number',
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',
},
}
+1 -1
View File
@@ -13,7 +13,7 @@ export const DocuSignBlock: BlockConfig<DocuSignResponse> = {
'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,
+2
View File
@@ -13,6 +13,7 @@ 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 { BrandfetchBlock } from '@/blocks/blocks/brandfetch'
import { BrowserUseBlock } from '@/blocks/blocks/browser_use'
import { CalComBlock } from '@/blocks/blocks/calcom'
@@ -217,6 +218,7 @@ export const registry: Record<string, BlockConfig> = {
ashby: AshbyBlock,
attio: AttioBlock,
brandfetch: BrandfetchBlock,
box: BoxBlock,
browser_use: BrowserUseBlock,
calcom: CalComBlock,
calendly: CalendlyBlock,
+11 -5
View File
@@ -4616,11 +4616,17 @@ export function ShopifyIcon(props: SVGProps<SVGSVGElement>) {
export function BoxCompanyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 41 22'>
<path
d='M39.7 19.2c.5.7.4 1.6-.2 2.1-.7.5-1.7.4-2.2-.2l-3.5-4.5-3.4 4.4c-.5.7-1.5.7-2.2.2-.7-.5-.8-1.4-.3-2.1l4-5.2-4-5.2c-.5-.7-.3-1.7.3-2.2.7-.5 1.7-.3 2.2.3l3.4 4.5L37.3 7c.5-.7 1.4-.8 2.2-.3.7.5.7 1.5.2 2.2L35.8 14l3.9 5.2zm-18.2-.6c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c-.1 2.6-2.2 4.6-4.7 4.6zm-13.8 0c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c0 2.6-2.1 4.6-4.7 4.6zM21.5 6.4c-2.9 0-5.5 1.6-6.8 4-1.3-2.4-3.9-4-6.9-4-1.8 0-3.4.6-4.7 1.5V1.5C3.1.7 2.4 0 1.6 0 .7 0 0 .7 0 1.5v12.6c.1 4.2 3.5 7.5 7.7 7.5 3 0 5.6-1.7 6.9-4.1 1.3 2.4 3.9 4.1 6.8 4.1 4.3 0 7.8-3.4 7.8-7.7.1-4.1-3.4-7.5-7.7-7.5z'
fill='currentColor'
/>
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
width='2500'
height='1379'
viewBox='0 0 444.893 245.414'
>
<g fill='#0075C9'>
<path d='M239.038 72.43c-33.081 0-61.806 18.6-76.322 45.904-14.516-27.305-43.24-45.902-76.32-45.902-19.443 0-37.385 6.424-51.821 17.266V16.925h-.008C34.365 7.547 26.713 0 17.286 0 7.858 0 .208 7.547.008 16.925H0v143.333h.036c.768 47.051 39.125 84.967 86.359 84.967 33.08 0 61.805-18.603 76.32-45.908 14.517 27.307 43.241 45.906 76.321 45.906 47.715 0 86.396-38.684 86.396-86.396.001-47.718-38.682-86.397-86.394-86.397zM86.395 210.648c-28.621 0-51.821-23.201-51.821-51.82 0-28.623 23.201-51.823 51.821-51.823 28.621 0 51.822 23.2 51.822 51.823 0 28.619-23.201 51.82-51.822 51.82zm152.643 0c-28.622 0-51.821-23.201-51.821-51.822 0-28.623 23.2-51.821 51.821-51.821 28.619 0 51.822 23.198 51.822 51.821-.001 28.621-23.203 51.822-51.822 51.822z' />
<path d='M441.651 218.033l-44.246-59.143 44.246-59.144-.008-.007c5.473-7.62 3.887-18.249-3.652-23.913-7.537-5.658-18.187-4.221-23.98 3.157l-.004-.002-38.188 51.047-38.188-51.047-.006.009c-5.793-7.385-16.441-8.822-23.981-3.16-7.539 5.664-9.125 16.293-3.649 23.911l-.008.005 44.245 59.144-44.245 59.143.008.005c-5.477 7.62-3.89 18.247 3.649 23.909 7.54 5.664 18.188 4.225 23.981-3.155l.006.007 38.188-51.049 38.188 51.049.004-.002c5.794 7.377 16.443 8.814 23.98 3.154 7.539-5.662 9.125-16.291 3.652-23.91l.008-.008z' />
</g>
</svg>
)
}
+60 -4
View File
@@ -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) => {
@@ -478,6 +488,7 @@ export const auth = betterAuth({
'sharepoint',
'jira',
'airtable',
'box',
'dropbox',
'salesforce',
'wealthbox',
@@ -2177,6 +2188,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,
@@ -2599,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',
@@ -2611,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}`,
},
+2
View File
@@ -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
+26 -1
View File
@@ -3,6 +3,7 @@ import {
AirtableIcon,
AsanaIcon,
AttioIcon,
BoxCompanyIcon,
CalComIcon,
ConfluenceIcon,
DocuSignIcon,
@@ -572,6 +573,21 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
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', 'sign_requests.readwrite'],
},
},
defaultService: 'box',
},
dropbox: {
name: 'Dropbox',
icon: DropboxIcon,
@@ -1125,13 +1141,22 @@ 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,
env.DOCUSIGN_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://account.docusign.com/oauth/token',
tokenEndpoint: 'https://account-d.docusign.com/oauth/token',
clientId,
clientSecret,
useBasicAuth: true,
+2
View File
@@ -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'
+1
View File
@@ -342,6 +342,7 @@ export const SCOPE_DESCRIPTIONS: Record<string, string> = {
// 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',
+82
View File
@@ -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<BoxCopyFileParams, BoxUploadFileResponse> = {
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<string, unknown> = {
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,
}
+71
View File
@@ -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<BoxCreateFolderParams, BoxFolderResponse> = {
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,
}
+57
View File
@@ -0,0 +1,57 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
import type { BoxDeleteFileParams } from './types'
export const boxDeleteFileTool: ToolConfig<BoxDeleteFileParams, ToolResponse> = {
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' },
},
}
+68
View File
@@ -0,0 +1,68 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
import type { BoxDeleteFolderParams } from './types'
export const boxDeleteFolderTool: ToolConfig<BoxDeleteFolderParams, ToolResponse> = {
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' },
},
}
+87
View File
@@ -0,0 +1,87 @@
import type { ToolConfig } from '@/tools/types'
import type { BoxDownloadFileParams, BoxDownloadFileResponse } from './types'
export const boxDownloadFileTool: ToolConfig<BoxDownloadFileParams, BoxDownloadFileResponse> = {
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',
},
},
}
+87
View File
@@ -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<BoxGetFileInfoParams, BoxFileInfoResponse> = {
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,
}
+10
View File
@@ -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'
+98
View File
@@ -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<BoxListFolderItemsParams, BoxFolderItemsResponse> =
{
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: {
entries: (data.entries ?? []).map((item: Record<string, unknown>) => ({
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,
}
+105
View File
@@ -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<BoxSearchParams, BoxSearchResponse> = {
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<string, unknown>) => ({
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<string, unknown> | undefined)?.id ?? null,
parentName: (item.parent as Record<string, unknown> | undefined)?.name ?? null,
})),
totalCount: data.total_count ?? 0,
},
}
},
outputs: SEARCH_RESULT_OUTPUT_PROPERTIES,
}
+249
View File
@@ -0,0 +1,249 @@
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<string, unknown> | null
tags: string[]
commentCount: number | null
}
}
export interface BoxFolderItemsResponse extends ToolResponse {
output: {
entries: Array<Record<string, unknown>>
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<Record<string, unknown>>
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<string, OutputProperty>
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<string, OutputProperty>
export const FOLDER_ITEMS_OUTPUT_PROPERTIES = {
entries: {
type: 'array',
description: 'List of items in the folder',
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 },
},
},
},
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<string, OutputProperty>
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<string, OutputProperty>
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<string, OutputProperty>
export const UPLOAD_FILE_OUTPUT_PROPERTIES = {
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 },
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<string, OutputProperty>
+124
View File
@@ -0,0 +1,124 @@
import type { ToolConfig } from '@/tools/types'
import type { BoxFileInfoResponse, BoxUpdateFileParams } from './types'
import { FILE_OUTPUT_PROPERTIES } from './types'
export const boxUpdateFileTool: ToolConfig<BoxUpdateFileParams, BoxFileInfoResponse> = {
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<string, unknown> = {}
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())
.filter(Boolean)
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,
}
+78
View File
@@ -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<BoxUploadFileParams, BoxUploadFileResponse> = {
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,
}
+80
View File
@@ -0,0 +1,80 @@
import type { ToolConfig } from '@/tools/types'
import type { BoxSignCancelRequestParams, BoxSignResponse } from './types'
import { SIGN_REQUEST_OUTPUT_PROPERTIES } from './types'
export const boxSignCancelRequestTool: ToolConfig<BoxSignCancelRequestParams, BoxSignResponse> = {
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',
},
},
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: () => ({}),
},
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<string, unknown>) => ({
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<string, unknown>) => ({
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,
}
+225
View File
@@ -0,0 +1,225 @@
import type { ToolConfig } from '@/tools/types'
import type { BoxSignCreateRequestParams, BoxSignResponse } from './types'
import { SIGN_REQUEST_OUTPUT_PROPERTIES } from './types'
export const boxSignCreateRequestTool: ToolConfig<BoxSignCreateRequestParams, BoxSignResponse> = {
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())
.filter(Boolean)
const sourceFiles = fileIds.map((id: string) => ({ type: 'file', id }))
const signers: Array<Record<string, unknown>> = [
{
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 {
throw new Error(
'Invalid JSON in additionalSigners. Expected a JSON array of signer objects.'
)
}
}
const body: Record<string, unknown> = {
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<string, unknown>) => ({
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<string, unknown>) => ({
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,
}
+78
View File
@@ -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<BoxSignGetRequestParams, BoxSignResponse> = {
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<string, unknown>) => ({
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<string, unknown>) => ({
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,
}
+5
View File
@@ -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'
+100
View File
@@ -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<BoxSignListRequestsParams, BoxSignListResponse> = {
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<string, unknown>) => ({
id: req.id ?? '',
status: req.status ?? '',
name: req.name ?? null,
shortId: req.short_id ?? null,
signers: ((req.signers as Array<Record<string, unknown>> | undefined) ?? []).map(
(s: Record<string, unknown>) => ({
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<Record<string, unknown>> | undefined) ?? []).map(
(f: Record<string, unknown>) => ({
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,
}
+55
View File
@@ -0,0 +1,55 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
import type { BoxSignResendRequestParams } from './types'
export const boxSignResendRequestTool: ToolConfig<BoxSignResendRequestParams, ToolResponse> = {
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' },
},
}
+162
View File
@@ -0,0 +1,162 @@
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
}
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<Record<string, unknown>>
sourceFiles: Array<Record<string, unknown>>
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<Record<string, unknown>>
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<string, OutputProperty>
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<string, OutputProperty>
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<string, OutputProperty>
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<string, OutputProperty>
+34
View File
@@ -168,6 +168,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 {
@@ -2451,6 +2470,21 @@ export const tools: Record<string, ToolConfig> = {
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,