mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(docusign): add docusign integration (#3661)
* feat(docusign): add DocuSign e-signature integration * fix(docusign): add base_uri null check and move file normalization to params * fix(docusign): use canonical param documentFile instead of raw subBlock IDs * fix(docusign): validate document file is present before sending envelope * fix(docusign): rename tool files from kebab-case to snake_case for docs generation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1146,6 +1146,25 @@ export function DevinIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function DocuSignIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 1547 1549' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='m1113.4 1114.9v395.6c0 20.8-16.7 37.6-37.5 37.6h-1038.4c-20.7 0-37.5-16.8-37.5-37.6v-1039c0-20.7 16.8-37.5 37.5-37.5h394.3v643.4c0 20.7 16.8 37.5 37.5 37.5z'
|
||||
fill='#4c00ff'
|
||||
/>
|
||||
<path
|
||||
d='m1546 557.1c0 332.4-193.9 557-432.6 557.8v-418.8c0-12-4.8-24-13.5-31.9l-217.1-217.4c-8.8-8.8-20-13.6-32-13.6h-418.2v-394.8c0-20.8 16.8-37.6 37.5-37.6h585.1c277.7-0.8 490.8 223 490.8 556.3z'
|
||||
fill='#ff5252'
|
||||
/>
|
||||
<path
|
||||
d='m1099.9 663.4c8.7 8.7 13.5 19.9 13.5 31.9v418.8h-643.3c-20.7 0-37.5-16.8-37.5-37.5v-643.4h418.2c12 0 24 4.8 32 13.6z'
|
||||
fill='#000000'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
DevinIcon,
|
||||
DiscordIcon,
|
||||
DocumentIcon,
|
||||
DocuSignIcon,
|
||||
DropboxIcon,
|
||||
DsPyIcon,
|
||||
DubIcon,
|
||||
@@ -198,6 +199,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
datadog: DatadogIcon,
|
||||
devin: DevinIcon,
|
||||
discord: DiscordIcon,
|
||||
docusign: DocuSignIcon,
|
||||
dropbox: DropboxIcon,
|
||||
dspy: DsPyIcon,
|
||||
dub: DubIcon,
|
||||
|
||||
229
apps/docs/content/docs/en/tools/docusign.mdx
Normal file
229
apps/docs/content/docs/en/tools/docusign.mdx
Normal file
@@ -0,0 +1,229 @@
|
||||
---
|
||||
title: DocuSign
|
||||
description: Send documents for e-signature via DocuSign
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="docusign"
|
||||
color="#4C00FF"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[DocuSign](https://www.docusign.com) is the world's leading e-signature platform, enabling businesses to send, sign, and manage agreements digitally. With its powerful eSignature REST API, DocuSign supports the full document lifecycle from creation through completion.
|
||||
|
||||
With the DocuSign integration in Sim, you can:
|
||||
|
||||
- **Send envelopes**: Create and send documents for e-signature with custom recipients and signing tabs
|
||||
- **Use templates**: Send envelopes from pre-configured DocuSign templates with role assignments
|
||||
- **Track status**: Get envelope details including signing progress, timestamps, and recipient status
|
||||
- **List envelopes**: Search and filter envelopes by date range, status, and text
|
||||
- **Download documents**: Retrieve signed documents as base64-encoded files
|
||||
- **Manage recipients**: View signer and CC recipient details and signing status
|
||||
- **Void envelopes**: Cancel in-progress envelopes with a reason
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `docusign_send_envelope`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_create_from_template`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_get_envelope`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_list_envelopes`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_void_envelope`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_download_document`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_list_templates`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
### `docusign_list_recipients`
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### 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 |
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"datadog",
|
||||
"devin",
|
||||
"discord",
|
||||
"docusign",
|
||||
"dropbox",
|
||||
"dspy",
|
||||
"dub",
|
||||
|
||||
466
apps/sim/app/api/tools/docusign/route.ts
Normal file
466
apps/sim/app/api/tools/docusign/route.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
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'
|
||||
|
||||
const logger = createLogger('DocuSignAPI')
|
||||
|
||||
interface DocuSignAccountInfo {
|
||||
accountId: string
|
||||
baseUri: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the user's DocuSign account info from their access token
|
||||
* by calling the DocuSign userinfo endpoint.
|
||||
*/
|
||||
async function resolveAccount(accessToken: string): Promise<DocuSignAccountInfo> {
|
||||
const response = await fetch('https://account.docusign.com/oauth/userinfo', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('Failed to resolve DocuSign account', {
|
||||
status: response.status,
|
||||
error: errorText,
|
||||
})
|
||||
throw new Error(`Failed to resolve DocuSign account: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const accounts = data.accounts ?? []
|
||||
|
||||
const defaultAccount = accounts.find((a: { is_default: boolean }) => a.is_default) ?? accounts[0]
|
||||
if (!defaultAccount) {
|
||||
throw new Error('No DocuSign accounts found for this user')
|
||||
}
|
||||
|
||||
const baseUri = defaultAccount.base_uri
|
||||
if (!baseUri) {
|
||||
throw new Error('DocuSign account is missing base_uri')
|
||||
}
|
||||
|
||||
return {
|
||||
accountId: defaultAccount.account_id,
|
||||
baseUri,
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { accessToken, operation, ...params } = body
|
||||
|
||||
if (!accessToken) {
|
||||
return NextResponse.json({ success: false, error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!operation) {
|
||||
return NextResponse.json({ success: false, error: 'Operation is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
try {
|
||||
const account = await resolveAccount(accessToken)
|
||||
const apiBase = `${account.baseUri}/restapi/v2.1/accounts/${account.accountId}`
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
switch (operation) {
|
||||
case 'send_envelope':
|
||||
return await handleSendEnvelope(apiBase, headers, params)
|
||||
case 'create_from_template':
|
||||
return await handleCreateFromTemplate(apiBase, headers, params)
|
||||
case 'get_envelope':
|
||||
return await handleGetEnvelope(apiBase, headers, params)
|
||||
case 'list_envelopes':
|
||||
return await handleListEnvelopes(apiBase, headers, params)
|
||||
case 'void_envelope':
|
||||
return await handleVoidEnvelope(apiBase, headers, params)
|
||||
case 'download_document':
|
||||
return await handleDownloadDocument(apiBase, headers, params)
|
||||
case 'list_templates':
|
||||
return await handleListTemplates(apiBase, headers, params)
|
||||
case 'list_recipients':
|
||||
return await handleListRecipients(apiBase, headers, params)
|
||||
default:
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `Unknown operation: ${operation}` },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('DocuSign API error', { operation, error })
|
||||
const message = error instanceof Error ? error.message : 'Internal server error'
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSendEnvelope(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const { signerEmail, signerName, emailSubject, emailBody, ccEmail, ccName, file, status } = params
|
||||
|
||||
if (!signerEmail || !signerName || !emailSubject) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'signerEmail, signerName, and emailSubject are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
let documentBase64 = ''
|
||||
let documentName = 'document.pdf'
|
||||
|
||||
if (file) {
|
||||
try {
|
||||
const parsed = FileInputSchema.parse(file)
|
||||
const userFiles = processFilesToUserFiles([parsed as RawFileInput], 'docusign-send', logger)
|
||||
if (userFiles.length > 0) {
|
||||
const userFile = userFiles[0]
|
||||
const buffer = await downloadFileFromStorage(userFile, 'docusign-send', logger)
|
||||
documentBase64 = buffer.toString('base64')
|
||||
documentName = userFile.name
|
||||
}
|
||||
} catch (fileError) {
|
||||
logger.error('Failed to process file for DocuSign envelope', { fileError })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Failed to process uploaded file' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const envelopeBody: Record<string, unknown> = {
|
||||
emailSubject,
|
||||
status: (status as string) || 'sent',
|
||||
recipients: {
|
||||
signers: [
|
||||
{
|
||||
email: signerEmail,
|
||||
name: signerName,
|
||||
recipientId: '1',
|
||||
routingOrder: '1',
|
||||
tabs: {
|
||||
signHereTabs: [
|
||||
{
|
||||
anchorString: '/sig1/',
|
||||
anchorUnits: 'pixels',
|
||||
anchorXOffset: '0',
|
||||
anchorYOffset: '0',
|
||||
},
|
||||
],
|
||||
dateSignedTabs: [
|
||||
{
|
||||
anchorString: '/date1/',
|
||||
anchorUnits: 'pixels',
|
||||
anchorXOffset: '0',
|
||||
anchorYOffset: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
carbonCopies: ccEmail
|
||||
? [
|
||||
{
|
||||
email: ccEmail,
|
||||
name: ccName || (ccEmail as string),
|
||||
recipientId: '2',
|
||||
routingOrder: '2',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
}
|
||||
|
||||
if (emailBody) {
|
||||
envelopeBody.emailBlurb = emailBody
|
||||
}
|
||||
|
||||
if (documentBase64) {
|
||||
envelopeBody.documents = [
|
||||
{
|
||||
documentBase64,
|
||||
name: documentName,
|
||||
fileExtension: documentName.split('.').pop() || 'pdf',
|
||||
documentId: '1',
|
||||
},
|
||||
]
|
||||
} else if (((status as string) || 'sent') === 'sent') {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'A document file is required to send an envelope' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiBase}/envelopes`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(envelopeBody),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
logger.error('DocuSign send envelope failed', { data, status: response.status })
|
||||
return NextResponse.json(
|
||||
{ success: false, error: data.message || data.errorCode || 'Failed to send envelope' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
|
||||
async function handleCreateFromTemplate(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const { templateId, emailSubject, emailBody, templateRoles, status } = params
|
||||
|
||||
if (!templateId) {
|
||||
return NextResponse.json({ success: false, error: 'templateId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
let parsedRoles: unknown[] = []
|
||||
if (templateRoles) {
|
||||
if (typeof templateRoles === 'string') {
|
||||
try {
|
||||
parsedRoles = JSON.parse(templateRoles)
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Invalid JSON for templateRoles' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
} else if (Array.isArray(templateRoles)) {
|
||||
parsedRoles = templateRoles
|
||||
}
|
||||
}
|
||||
|
||||
const envelopeBody: Record<string, unknown> = {
|
||||
templateId,
|
||||
status: (status as string) || 'sent',
|
||||
templateRoles: parsedRoles,
|
||||
}
|
||||
|
||||
if (emailSubject) envelopeBody.emailSubject = emailSubject
|
||||
if (emailBody) envelopeBody.emailBlurb = emailBody
|
||||
|
||||
const response = await fetch(`${apiBase}/envelopes`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(envelopeBody),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
logger.error('DocuSign create from template failed', { data, status: response.status })
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: data.message || data.errorCode || 'Failed to create envelope from template',
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
|
||||
async function handleGetEnvelope(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const { envelopeId } = params
|
||||
if (!envelopeId) {
|
||||
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${apiBase}/envelopes/${(envelopeId as string).trim()}?include=recipients,documents`,
|
||||
{ headers }
|
||||
)
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: data.message || data.errorCode || 'Failed to get envelope' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
|
||||
async function handleListEnvelopes(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
const fromDate = params.fromDate as string | undefined
|
||||
if (fromDate) {
|
||||
queryParams.append('from_date', fromDate)
|
||||
} else {
|
||||
const thirtyDaysAgo = new Date()
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
|
||||
queryParams.append('from_date', thirtyDaysAgo.toISOString())
|
||||
}
|
||||
|
||||
if (params.toDate) queryParams.append('to_date', params.toDate as string)
|
||||
if (params.envelopeStatus) queryParams.append('status', params.envelopeStatus as string)
|
||||
if (params.searchText) queryParams.append('search_text', params.searchText as string)
|
||||
if (params.count) queryParams.append('count', params.count as string)
|
||||
|
||||
const response = await fetch(`${apiBase}/envelopes?${queryParams}`, { headers })
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: data.message || data.errorCode || 'Failed to list envelopes' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
|
||||
async function handleVoidEnvelope(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const { envelopeId, voidedReason } = params
|
||||
if (!envelopeId) {
|
||||
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
|
||||
}
|
||||
if (!voidedReason) {
|
||||
return NextResponse.json({ success: false, error: 'voidedReason is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiBase}/envelopes/${(envelopeId as string).trim()}`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({ status: 'voided', voidedReason }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: data.message || data.errorCode || 'Failed to void envelope' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ envelopeId, status: 'voided' })
|
||||
}
|
||||
|
||||
async function handleDownloadDocument(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const { envelopeId, documentId } = params
|
||||
if (!envelopeId) {
|
||||
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const docId = (documentId as string) || 'combined'
|
||||
|
||||
const response = await fetch(
|
||||
`${apiBase}/envelopes/${(envelopeId as string).trim()}/documents/${docId}`,
|
||||
{
|
||||
headers: { Authorization: headers.Authorization },
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
let errorText = ''
|
||||
try {
|
||||
errorText = await response.text()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ success: false, error: `Failed to download document: ${response.status} ${errorText}` },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || 'application/pdf'
|
||||
const contentDisposition = response.headers.get('content-disposition') || ''
|
||||
let fileName = `document-${docId}.pdf`
|
||||
|
||||
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||
if (filenameMatch) {
|
||||
fileName = filenameMatch[1].replace(/['"]/g, '')
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await response.arrayBuffer())
|
||||
const base64Content = buffer.toString('base64')
|
||||
|
||||
return NextResponse.json({ base64Content, mimeType: contentType, fileName })
|
||||
}
|
||||
|
||||
async function handleListTemplates(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.searchText) queryParams.append('search_text', params.searchText as string)
|
||||
if (params.count) queryParams.append('count', params.count as string)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const url = queryString ? `${apiBase}/templates?${queryString}` : `${apiBase}/templates`
|
||||
|
||||
const response = await fetch(url, { headers })
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: data.message || data.errorCode || 'Failed to list templates' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
|
||||
async function handleListRecipients(
|
||||
apiBase: string,
|
||||
headers: Record<string, string>,
|
||||
params: Record<string, unknown>
|
||||
) {
|
||||
const { envelopeId } = params
|
||||
if (!envelopeId) {
|
||||
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiBase}/envelopes/${(envelopeId as string).trim()}/recipients`, {
|
||||
headers,
|
||||
})
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: data.message || data.errorCode || 'Failed to list recipients' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(data)
|
||||
}
|
||||
372
apps/sim/blocks/blocks/docusign.ts
Normal file
372
apps/sim/blocks/blocks/docusign.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { DocuSignIcon } 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'
|
||||
import type { DocuSignResponse } from '@/tools/docusign/types'
|
||||
|
||||
export const DocuSignBlock: BlockConfig<DocuSignResponse> = {
|
||||
type: 'docusign',
|
||||
name: 'DocuSign',
|
||||
description: 'Send documents for e-signature via DocuSign',
|
||||
longDescription:
|
||||
'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',
|
||||
icon: DocuSignIcon,
|
||||
authMode: AuthMode.OAuth,
|
||||
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Send Envelope', id: 'send_envelope' },
|
||||
{ label: 'Send from Template', id: 'create_from_template' },
|
||||
{ label: 'Get Envelope', id: 'get_envelope' },
|
||||
{ label: 'List Envelopes', id: 'list_envelopes' },
|
||||
{ label: 'Void Envelope', id: 'void_envelope' },
|
||||
{ label: 'Download Document', id: 'download_document' },
|
||||
{ label: 'List Templates', id: 'list_templates' },
|
||||
{ label: 'List Recipients', id: 'list_recipients' },
|
||||
],
|
||||
value: () => 'send_envelope',
|
||||
},
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'DocuSign Account',
|
||||
type: 'oauth-input',
|
||||
canonicalParamId: 'oauthCredential',
|
||||
mode: 'basic',
|
||||
serviceId: 'docusign',
|
||||
requiredScopes: getScopesForService('docusign'),
|
||||
placeholder: 'Select DocuSign account',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'manualCredential',
|
||||
title: 'DocuSign Account',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'oauthCredential',
|
||||
mode: 'advanced',
|
||||
placeholder: 'Enter credential ID',
|
||||
required: true,
|
||||
},
|
||||
|
||||
// Send Envelope fields
|
||||
{
|
||||
id: 'emailSubject',
|
||||
title: 'Email Subject',
|
||||
type: 'short-input',
|
||||
placeholder: 'Please sign this document',
|
||||
condition: { field: 'operation', value: ['send_envelope', 'create_from_template'] },
|
||||
required: { field: 'operation', value: 'send_envelope' },
|
||||
},
|
||||
{
|
||||
id: 'emailBody',
|
||||
title: 'Email Body',
|
||||
type: 'long-input',
|
||||
placeholder: 'Optional message to include in the email',
|
||||
condition: { field: 'operation', value: ['send_envelope', 'create_from_template'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'signerEmail',
|
||||
title: 'Signer Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'signer@example.com',
|
||||
condition: { field: 'operation', value: 'send_envelope' },
|
||||
required: { field: 'operation', value: 'send_envelope' },
|
||||
},
|
||||
{
|
||||
id: 'signerName',
|
||||
title: 'Signer Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'John Doe',
|
||||
condition: { field: 'operation', value: 'send_envelope' },
|
||||
required: { field: 'operation', value: 'send_envelope' },
|
||||
},
|
||||
{
|
||||
id: 'uploadDocument',
|
||||
title: 'Document',
|
||||
type: 'file-upload',
|
||||
canonicalParamId: 'documentFile',
|
||||
placeholder: 'Upload document for signature',
|
||||
mode: 'basic',
|
||||
multiple: false,
|
||||
condition: { field: 'operation', value: 'send_envelope' },
|
||||
},
|
||||
{
|
||||
id: 'documentRef',
|
||||
title: 'Document',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'documentFile',
|
||||
placeholder: 'Reference file from another block',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'send_envelope' },
|
||||
},
|
||||
{
|
||||
id: 'ccEmail',
|
||||
title: 'CC Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'cc@example.com',
|
||||
condition: { field: 'operation', value: 'send_envelope' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'ccName',
|
||||
title: 'CC Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'CC recipient name',
|
||||
condition: { field: 'operation', value: 'send_envelope' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'envelopeStatus',
|
||||
title: 'Status',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Send Immediately', id: 'sent' },
|
||||
{ label: 'Save as Draft', id: 'created' },
|
||||
],
|
||||
value: () => 'sent',
|
||||
condition: { field: 'operation', value: ['send_envelope', 'create_from_template'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// Send from Template fields
|
||||
{
|
||||
id: 'templateId',
|
||||
title: 'Template ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'DocuSign template ID',
|
||||
condition: { field: 'operation', value: 'create_from_template' },
|
||||
required: { field: 'operation', value: 'create_from_template' },
|
||||
},
|
||||
{
|
||||
id: 'templateRoles',
|
||||
title: 'Template Roles',
|
||||
type: 'long-input',
|
||||
placeholder: '[{"roleName":"Signer","name":"John Doe","email":"john@example.com"}]',
|
||||
condition: { field: 'operation', value: 'create_from_template' },
|
||||
required: { field: 'operation', value: 'create_from_template' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt:
|
||||
'Generate a JSON array of DocuSign template role objects. Each role needs: roleName (must match the template role name), name (full name), email (email address). Return ONLY the JSON array.',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
|
||||
// Envelope ID field (shared across multiple operations)
|
||||
{
|
||||
id: 'envelopeId',
|
||||
title: 'Envelope ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'DocuSign envelope ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_envelope', 'void_envelope', 'download_document', 'list_recipients'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['get_envelope', 'void_envelope', 'download_document', 'list_recipients'],
|
||||
},
|
||||
},
|
||||
|
||||
// Void Envelope fields
|
||||
{
|
||||
id: 'voidedReason',
|
||||
title: 'Void Reason',
|
||||
type: 'short-input',
|
||||
placeholder: 'Reason for voiding this envelope',
|
||||
condition: { field: 'operation', value: 'void_envelope' },
|
||||
required: { field: 'operation', value: 'void_envelope' },
|
||||
},
|
||||
|
||||
// Download Document fields
|
||||
{
|
||||
id: 'documentId',
|
||||
title: 'Document ID',
|
||||
type: 'short-input',
|
||||
placeholder: '"combined" for all docs, or specific document ID',
|
||||
condition: { field: 'operation', value: 'download_document' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// List Envelopes filters
|
||||
{
|
||||
id: 'fromDate',
|
||||
title: 'From Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISO 8601 date (defaults to 30 days ago)',
|
||||
condition: { field: 'operation', value: 'list_envelopes' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'toDate',
|
||||
title: 'To Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISO 8601 date',
|
||||
condition: { field: 'operation', value: 'list_envelopes' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'listEnvelopeStatus',
|
||||
title: 'Status Filter',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: 'Created', id: 'created' },
|
||||
{ label: 'Sent', id: 'sent' },
|
||||
{ label: 'Delivered', id: 'delivered' },
|
||||
{ label: 'Completed', id: 'completed' },
|
||||
{ label: 'Declined', id: 'declined' },
|
||||
{ label: 'Voided', id: 'voided' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: 'list_envelopes' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'searchText',
|
||||
title: 'Search',
|
||||
type: 'short-input',
|
||||
placeholder: 'Search envelopes or templates',
|
||||
condition: { field: 'operation', value: ['list_envelopes', 'list_templates'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'count',
|
||||
title: 'Max Results',
|
||||
type: 'short-input',
|
||||
placeholder: '25',
|
||||
condition: { field: 'operation', value: ['list_envelopes', 'list_templates'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
],
|
||||
|
||||
tools: {
|
||||
access: [
|
||||
'docusign_send_envelope',
|
||||
'docusign_create_from_template',
|
||||
'docusign_get_envelope',
|
||||
'docusign_list_envelopes',
|
||||
'docusign_void_envelope',
|
||||
'docusign_download_document',
|
||||
'docusign_list_templates',
|
||||
'docusign_list_recipients',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `docusign_${params.operation}`,
|
||||
params: (params) => {
|
||||
const { oauthCredential, operation, documentFile, listEnvelopeStatus, ...rest } = params
|
||||
|
||||
const cleanParams: Record<string, unknown> = {
|
||||
oauthCredential,
|
||||
}
|
||||
|
||||
const file = normalizeFileInput(documentFile, { single: true })
|
||||
if (file) {
|
||||
cleanParams.file = file
|
||||
}
|
||||
|
||||
if (listEnvelopeStatus && operation === 'list_envelopes') {
|
||||
cleanParams.envelopeStatus = listEnvelopeStatus
|
||||
}
|
||||
|
||||
if (operation === 'create_from_template') {
|
||||
cleanParams.status = rest.envelopeStatus
|
||||
} else if (operation === 'send_envelope') {
|
||||
cleanParams.status = rest.envelopeStatus
|
||||
}
|
||||
|
||||
const excludeKeys = ['envelopeStatus']
|
||||
for (const [key, value] of Object.entries(rest)) {
|
||||
if (value !== undefined && value !== null && value !== '' && !excludeKeys.includes(key)) {
|
||||
cleanParams[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return cleanParams
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
oauthCredential: { type: 'string', description: 'DocuSign access token' },
|
||||
emailSubject: { type: 'string', description: 'Email subject for the envelope' },
|
||||
emailBody: { type: 'string', description: 'Email body message' },
|
||||
signerEmail: { type: 'string', description: 'Signer email address' },
|
||||
signerName: { type: 'string', description: 'Signer full name' },
|
||||
documentFile: { type: 'string', description: 'Document file for signature' },
|
||||
ccEmail: { type: 'string', description: 'CC recipient email' },
|
||||
ccName: { type: 'string', description: 'CC recipient name' },
|
||||
templateId: { type: 'string', description: 'DocuSign template ID' },
|
||||
templateRoles: { type: 'string', description: 'JSON array of template roles' },
|
||||
envelopeId: { type: 'string', description: 'Envelope ID' },
|
||||
voidedReason: { type: 'string', description: 'Reason for voiding' },
|
||||
documentId: { type: 'string', description: 'Document ID to download' },
|
||||
fromDate: { type: 'string', description: 'Start date filter' },
|
||||
toDate: { type: 'string', description: 'End date filter' },
|
||||
searchText: { type: 'string', description: 'Search text filter' },
|
||||
count: { type: 'string', description: 'Max results to return' },
|
||||
},
|
||||
outputs: {
|
||||
envelopeId: { type: 'string', description: 'Envelope ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Envelope status (created, sent, delivered, completed, declined, voided)',
|
||||
},
|
||||
statusDateTime: { type: 'string', description: 'ISO 8601 datetime of status change' },
|
||||
uri: { type: 'string', description: 'Envelope URI path' },
|
||||
emailSubject: { type: 'string', description: 'Envelope email subject' },
|
||||
sentDateTime: { type: 'string', description: 'ISO 8601 datetime when envelope was sent' },
|
||||
completedDateTime: { type: 'string', description: 'ISO 8601 datetime when signing completed' },
|
||||
createdDateTime: { type: 'string', description: 'ISO 8601 datetime when envelope was created' },
|
||||
statusChangedDateTime: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime of last status change',
|
||||
},
|
||||
voidedReason: { type: 'string', description: 'Reason the envelope was voided' },
|
||||
signerCount: { type: 'number', description: 'Number of signers on the envelope' },
|
||||
documentCount: { type: 'number', description: 'Number of documents in the envelope' },
|
||||
envelopes: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of envelopes (envelopeId, status, emailSubject, sentDateTime, completedDateTime, createdDateTime, statusChangedDateTime)',
|
||||
},
|
||||
templates: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of templates (templateId, name, description, shared, created, lastModified)',
|
||||
},
|
||||
signers: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of signer recipients (recipientId, name, email, status, signedDateTime, deliveredDateTime)',
|
||||
},
|
||||
carbonCopies: {
|
||||
type: 'json',
|
||||
description: 'Array of CC recipients (recipientId, name, email, status)',
|
||||
},
|
||||
base64Content: { type: 'string', description: 'Base64-encoded document content' },
|
||||
mimeType: { type: 'string', description: 'Document MIME type' },
|
||||
fileName: { type: 'string', description: 'Document file name' },
|
||||
totalSetSize: { type: 'number', description: 'Total matching results' },
|
||||
resultSetSize: { type: 'number', description: 'Results returned in this response' },
|
||||
},
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { DatabricksBlock } from '@/blocks/blocks/databricks'
|
||||
import { DatadogBlock } from '@/blocks/blocks/datadog'
|
||||
import { DevinBlock } from '@/blocks/blocks/devin'
|
||||
import { DiscordBlock } from '@/blocks/blocks/discord'
|
||||
import { DocuSignBlock } from '@/blocks/blocks/docusign'
|
||||
import { DropboxBlock } from '@/blocks/blocks/dropbox'
|
||||
import { DSPyBlock } from '@/blocks/blocks/dspy'
|
||||
import { DubBlock } from '@/blocks/blocks/dub'
|
||||
@@ -232,6 +233,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
datadog: DatadogBlock,
|
||||
devin: DevinBlock,
|
||||
discord: DiscordBlock,
|
||||
docusign: DocuSignBlock,
|
||||
dropbox: DropboxBlock,
|
||||
dspy: DSPyBlock,
|
||||
dub: DubBlock,
|
||||
|
||||
@@ -1146,6 +1146,25 @@ export function DevinIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function DocuSignIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 1547 1549' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='m1113.4 1114.9v395.6c0 20.8-16.7 37.6-37.5 37.6h-1038.4c-20.7 0-37.5-16.8-37.5-37.6v-1039c0-20.7 16.8-37.5 37.5-37.5h394.3v643.4c0 20.7 16.8 37.5 37.5 37.5z'
|
||||
fill='#4c00ff'
|
||||
/>
|
||||
<path
|
||||
d='m1546 557.1c0 332.4-193.9 557-432.6 557.8v-418.8c0-12-4.8-24-13.5-31.9l-217.1-217.4c-8.8-8.8-20-13.6-32-13.6h-418.2v-394.8c0-20.8 16.8-37.6 37.5-37.6h585.1c277.7-0.8 490.8 223 490.8 556.3z'
|
||||
fill='#ff5252'
|
||||
/>
|
||||
<path
|
||||
d='m1099.9 663.4c8.7 8.7 13.5 19.9 13.5 31.9v418.8h-643.3c-20.7 0-37.5-16.8-37.5-37.5v-643.4h418.2c12 0 24 4.8 32 13.6z'
|
||||
fill='#000000'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -488,6 +488,7 @@ export const auth = betterAuth({
|
||||
'shopify',
|
||||
'trello',
|
||||
'calcom',
|
||||
'docusign',
|
||||
...SSO_TRUSTED_PROVIDERS,
|
||||
],
|
||||
},
|
||||
@@ -2593,6 +2594,64 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
// DocuSign provider
|
||||
{
|
||||
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',
|
||||
scopes: getCanonicalScopesForProvider('docusign'),
|
||||
responseType: 'code',
|
||||
accessType: 'offline',
|
||||
prompt: 'consent',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/docusign`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
logger.info('Fetching DocuSign user profile')
|
||||
|
||||
const response = await fetch('https://account.docusign.com/oauth/userinfo', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
await response.text().catch(() => {})
|
||||
logger.error('Failed to fetch DocuSign user info', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
throw new Error('Failed to fetch user info')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const accounts = data.accounts ?? []
|
||||
const defaultAccount =
|
||||
accounts.find((a: { is_default: boolean }) => a.is_default) ?? accounts[0]
|
||||
const accountName = defaultAccount?.account_name || 'DocuSign Account'
|
||||
|
||||
if (data.scope) {
|
||||
tokens.scopes = data.scope.split(/\s+/).filter(Boolean)
|
||||
}
|
||||
|
||||
return {
|
||||
id: `${data.sub}-${crypto.randomUUID()}`,
|
||||
name: data.name || accountName,
|
||||
email: data.email || `${data.sub}@docusign.com`,
|
||||
emailVerified: true,
|
||||
image: undefined,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in DocuSign getUserInfo:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Cal.com provider
|
||||
{
|
||||
providerId: 'calcom',
|
||||
|
||||
@@ -263,6 +263,8 @@ export const env = createEnv({
|
||||
NOTION_CLIENT_SECRET: z.string().optional(), // Notion OAuth client secret
|
||||
DISCORD_CLIENT_ID: z.string().optional(), // Discord OAuth client ID
|
||||
DISCORD_CLIENT_SECRET: z.string().optional(), // Discord OAuth client secret
|
||||
DOCUSIGN_CLIENT_ID: z.string().optional(), // DocuSign OAuth client ID
|
||||
DOCUSIGN_CLIENT_SECRET: z.string().optional(), // DocuSign OAuth client secret
|
||||
MICROSOFT_CLIENT_ID: z.string().optional(), // Microsoft OAuth client ID for Office 365/Teams
|
||||
MICROSOFT_CLIENT_SECRET: z.string().optional(), // Microsoft OAuth client secret
|
||||
HUBSPOT_CLIENT_ID: z.string().optional(), // HubSpot OAuth client ID
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
AttioIcon,
|
||||
CalComIcon,
|
||||
ConfluenceIcon,
|
||||
DocuSignIcon,
|
||||
DropboxIcon,
|
||||
GmailIcon,
|
||||
GoogleAdsIcon,
|
||||
@@ -779,6 +780,21 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
},
|
||||
defaultService: 'calcom',
|
||||
},
|
||||
docusign: {
|
||||
name: 'DocuSign',
|
||||
icon: DocuSignIcon,
|
||||
services: {
|
||||
docusign: {
|
||||
name: 'DocuSign',
|
||||
description: 'Send documents for e-signature with DocuSign.',
|
||||
providerId: 'docusign',
|
||||
icon: DocuSignIcon,
|
||||
baseProviderIcon: DocuSignIcon,
|
||||
scopes: ['signature', 'extended'],
|
||||
},
|
||||
},
|
||||
defaultService: 'docusign',
|
||||
},
|
||||
pipedrive: {
|
||||
name: 'Pipedrive',
|
||||
icon: PipedriveIcon,
|
||||
@@ -1109,6 +1125,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
useBasicAuth: false,
|
||||
}
|
||||
}
|
||||
case 'docusign': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.DOCUSIGN_CLIENT_ID,
|
||||
env.DOCUSIGN_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://account.docusign.com/oauth/token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: true,
|
||||
supportsRefreshTokenRotation: false,
|
||||
}
|
||||
}
|
||||
case 'dropbox': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.DROPBOX_CLIENT_ID,
|
||||
|
||||
@@ -47,6 +47,7 @@ export type OAuthProvider =
|
||||
| 'wordpress'
|
||||
| 'spotify'
|
||||
| 'calcom'
|
||||
| 'docusign'
|
||||
|
||||
export type OAuthService =
|
||||
| 'google'
|
||||
@@ -94,6 +95,7 @@ export type OAuthService =
|
||||
| 'wordpress'
|
||||
| 'spotify'
|
||||
| 'calcom'
|
||||
| 'docusign'
|
||||
| 'github'
|
||||
|
||||
export interface OAuthProviderConfig {
|
||||
|
||||
@@ -395,6 +395,10 @@ export const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'user-read-playback-position': 'View playback position in podcasts',
|
||||
'ugc-image-upload': 'Upload images to Spotify playlists',
|
||||
|
||||
// DocuSign scopes
|
||||
signature: 'Create and send envelopes for e-signature',
|
||||
extended: 'Extended access to DocuSign account features',
|
||||
|
||||
// Attio scopes
|
||||
'record_permission:read-write': 'Read and write CRM records',
|
||||
'object_configuration:read-write': 'Read and manage object schemas',
|
||||
|
||||
99
apps/sim/tools/docusign/create_from_template.ts
Normal file
99
apps/sim/tools/docusign/create_from_template.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type {
|
||||
DocuSignCreateFromTemplateParams,
|
||||
DocuSignCreateFromTemplateResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignCreateFromTemplateTool: ToolConfig<
|
||||
DocuSignCreateFromTemplateParams,
|
||||
DocuSignCreateFromTemplateResponse
|
||||
> = {
|
||||
id: 'docusign_create_from_template',
|
||||
name: 'Send from DocuSign Template',
|
||||
description: 'Create and send a DocuSign envelope using a pre-built template',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
templateId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'DocuSign template ID to use',
|
||||
},
|
||||
emailSubject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override email subject (uses template default if not set)',
|
||||
},
|
||||
emailBody: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override email body message',
|
||||
},
|
||||
templateRoles: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of template roles, e.g. [{"roleName":"Signer","name":"John","email":"john@example.com"}]',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Envelope status: "sent" to send immediately, "created" for draft (default: "sent")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'create_from_template',
|
||||
templateId: params.templateId,
|
||||
emailSubject: params.emailSubject,
|
||||
emailBody: params.emailBody,
|
||||
templateRoles: params.templateRoles,
|
||||
status: params.status,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to create envelope from template')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
envelopeId: data.envelopeId ?? null,
|
||||
status: data.status ?? null,
|
||||
statusDateTime: data.statusDateTime ?? null,
|
||||
uri: data.uri ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
envelopeId: { type: 'string', description: 'Created envelope ID' },
|
||||
status: { type: 'string', description: 'Envelope status' },
|
||||
statusDateTime: { type: 'string', description: 'Status change datetime', optional: true },
|
||||
uri: { type: 'string', description: 'Envelope URI', optional: true },
|
||||
},
|
||||
}
|
||||
75
apps/sim/tools/docusign/download_document.ts
Normal file
75
apps/sim/tools/docusign/download_document.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type {
|
||||
DocuSignDownloadDocumentParams,
|
||||
DocuSignDownloadDocumentResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignDownloadDocumentTool: ToolConfig<
|
||||
DocuSignDownloadDocumentParams,
|
||||
DocuSignDownloadDocumentResponse
|
||||
> = {
|
||||
id: 'docusign_download_document',
|
||||
name: 'Download DocuSign Document',
|
||||
description: 'Download a signed document from a completed DocuSign envelope',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
envelopeId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The envelope ID containing the document',
|
||||
},
|
||||
documentId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Specific document ID to download, or "combined" for all documents merged (default: "combined")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'download_document',
|
||||
envelopeId: params.envelopeId,
|
||||
documentId: params.documentId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to download document')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
base64Content: data.base64Content ?? '',
|
||||
mimeType: data.mimeType ?? 'application/pdf',
|
||||
fileName: data.fileName ?? 'document.pdf',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
base64Content: { type: 'string', description: 'Base64-encoded document content' },
|
||||
mimeType: { type: 'string', description: 'MIME type of the document' },
|
||||
fileName: { type: 'string', description: 'Original file name' },
|
||||
},
|
||||
}
|
||||
85
apps/sim/tools/docusign/get_envelope.ts
Normal file
85
apps/sim/tools/docusign/get_envelope.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { DocuSignGetEnvelopeParams, DocuSignGetEnvelopeResponse } from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignGetEnvelopeTool: ToolConfig<
|
||||
DocuSignGetEnvelopeParams,
|
||||
DocuSignGetEnvelopeResponse
|
||||
> = {
|
||||
id: 'docusign_get_envelope',
|
||||
name: 'Get DocuSign Envelope',
|
||||
description: 'Get the details and status of a DocuSign envelope',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
envelopeId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The envelope ID to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'get_envelope',
|
||||
envelopeId: params.envelopeId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to get envelope')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
envelopeId: data.envelopeId ?? null,
|
||||
status: data.status ?? null,
|
||||
emailSubject: data.emailSubject ?? null,
|
||||
sentDateTime: data.sentDateTime ?? null,
|
||||
completedDateTime: data.completedDateTime ?? null,
|
||||
createdDateTime: data.createdDateTime ?? null,
|
||||
statusChangedDateTime: data.statusChangedDateTime ?? null,
|
||||
voidedReason: data.voidedReason ?? null,
|
||||
signerCount: data.recipients?.signers?.length ?? 0,
|
||||
documentCount: data.envelopeDocuments?.length ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
envelopeId: { type: 'string', description: 'Envelope ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Envelope status (created, sent, delivered, completed, declined, voided)',
|
||||
},
|
||||
emailSubject: { type: 'string', description: 'Email subject line' },
|
||||
sentDateTime: { type: 'string', description: 'When the envelope was sent', optional: true },
|
||||
completedDateTime: {
|
||||
type: 'string',
|
||||
description: 'When all recipients completed signing',
|
||||
optional: true,
|
||||
},
|
||||
createdDateTime: { type: 'string', description: 'When the envelope was created' },
|
||||
statusChangedDateTime: { type: 'string', description: 'When the status last changed' },
|
||||
voidedReason: { type: 'string', description: 'Reason the envelope was voided', optional: true },
|
||||
signerCount: { type: 'number', description: 'Number of signers' },
|
||||
documentCount: { type: 'number', description: 'Number of documents' },
|
||||
},
|
||||
}
|
||||
9
apps/sim/tools/docusign/index.ts
Normal file
9
apps/sim/tools/docusign/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { docusignCreateFromTemplateTool } from './create_from_template'
|
||||
export { docusignDownloadDocumentTool } from './download_document'
|
||||
export { docusignGetEnvelopeTool } from './get_envelope'
|
||||
export { docusignListEnvelopesTool } from './list_envelopes'
|
||||
export { docusignListRecipientsTool } from './list_recipients'
|
||||
export { docusignListTemplatesTool } from './list_templates'
|
||||
export { docusignSendEnvelopeTool } from './send_envelope'
|
||||
export * from './types'
|
||||
export { docusignVoidEnvelopeTool } from './void_envelope'
|
||||
105
apps/sim/tools/docusign/list_envelopes.ts
Normal file
105
apps/sim/tools/docusign/list_envelopes.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type {
|
||||
DocuSignListEnvelopesParams,
|
||||
DocuSignListEnvelopesResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import { ENVELOPES_ARRAY_OUTPUT } from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignListEnvelopesTool: ToolConfig<
|
||||
DocuSignListEnvelopesParams,
|
||||
DocuSignListEnvelopesResponse
|
||||
> = {
|
||||
id: 'docusign_list_envelopes',
|
||||
name: 'List DocuSign Envelopes',
|
||||
description: 'List envelopes from your DocuSign account with optional filters',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
fromDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start date filter (ISO 8601). Defaults to 30 days ago',
|
||||
},
|
||||
toDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End date filter (ISO 8601)',
|
||||
},
|
||||
envelopeStatus: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by status: created, sent, delivered, completed, declined, voided',
|
||||
},
|
||||
searchText: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search text to filter envelopes',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of envelopes to return (default: 25)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'list_envelopes',
|
||||
fromDate: params.fromDate,
|
||||
toDate: params.toDate,
|
||||
envelopeStatus: params.envelopeStatus,
|
||||
searchText: params.searchText,
|
||||
count: params.count,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to list envelopes')
|
||||
}
|
||||
const envelopes = (data.envelopes ?? []).map((env: Record<string, unknown>) => ({
|
||||
envelopeId: env.envelopeId ?? null,
|
||||
status: env.status ?? null,
|
||||
emailSubject: env.emailSubject ?? null,
|
||||
sentDateTime: env.sentDateTime ?? null,
|
||||
completedDateTime: env.completedDateTime ?? null,
|
||||
createdDateTime: env.createdDateTime ?? null,
|
||||
statusChangedDateTime: env.statusChangedDateTime ?? null,
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
envelopes,
|
||||
totalSetSize: Number(data.totalSetSize) || 0,
|
||||
resultSetSize: Number(data.resultSetSize) || envelopes.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
envelopes: ENVELOPES_ARRAY_OUTPUT,
|
||||
totalSetSize: { type: 'number', description: 'Total number of matching envelopes' },
|
||||
resultSetSize: { type: 'number', description: 'Number of envelopes returned in this response' },
|
||||
},
|
||||
}
|
||||
92
apps/sim/tools/docusign/list_recipients.ts
Normal file
92
apps/sim/tools/docusign/list_recipients.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type {
|
||||
DocuSignListRecipientsParams,
|
||||
DocuSignListRecipientsResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import { RECIPIENTS_ARRAY_OUTPUT } from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignListRecipientsTool: ToolConfig<
|
||||
DocuSignListRecipientsParams,
|
||||
DocuSignListRecipientsResponse
|
||||
> = {
|
||||
id: 'docusign_list_recipients',
|
||||
name: 'List DocuSign Recipients',
|
||||
description: 'Get the recipient status details for a DocuSign envelope',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
envelopeId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The envelope ID to get recipients for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'list_recipients',
|
||||
envelopeId: params.envelopeId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to list recipients')
|
||||
}
|
||||
const signers = (data.signers ?? []).map((s: Record<string, unknown>) => ({
|
||||
recipientId: s.recipientId ?? null,
|
||||
name: s.name ?? null,
|
||||
email: s.email ?? null,
|
||||
status: s.status ?? null,
|
||||
signedDateTime: s.signedDateTime ?? null,
|
||||
deliveredDateTime: s.deliveredDateTime ?? null,
|
||||
}))
|
||||
const carbonCopies = (data.carbonCopies ?? []).map((cc: Record<string, unknown>) => ({
|
||||
recipientId: cc.recipientId ?? null,
|
||||
name: cc.name ?? null,
|
||||
email: cc.email ?? null,
|
||||
status: cc.status ?? null,
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
signers,
|
||||
carbonCopies,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
signers: RECIPIENTS_ARRAY_OUTPUT,
|
||||
carbonCopies: {
|
||||
type: 'array',
|
||||
description: 'Array of carbon copy recipients',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
recipientId: { type: 'string', description: 'Recipient ID' },
|
||||
name: { type: 'string', description: 'Recipient name' },
|
||||
email: { type: 'string', description: 'Recipient email' },
|
||||
status: { type: 'string', description: 'Recipient status' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
83
apps/sim/tools/docusign/list_templates.ts
Normal file
83
apps/sim/tools/docusign/list_templates.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type {
|
||||
DocuSignListTemplatesParams,
|
||||
DocuSignListTemplatesResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import { TEMPLATES_ARRAY_OUTPUT } from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignListTemplatesTool: ToolConfig<
|
||||
DocuSignListTemplatesParams,
|
||||
DocuSignListTemplatesResponse
|
||||
> = {
|
||||
id: 'docusign_list_templates',
|
||||
name: 'List DocuSign Templates',
|
||||
description: 'List available templates in your DocuSign account',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
searchText: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search text to filter templates by name',
|
||||
},
|
||||
count: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of templates to return',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'list_templates',
|
||||
searchText: params.searchText,
|
||||
count: params.count,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to list templates')
|
||||
}
|
||||
const templates = (data.envelopeTemplates ?? []).map((t: Record<string, unknown>) => ({
|
||||
templateId: t.templateId ?? null,
|
||||
name: t.name ?? null,
|
||||
description: t.description ?? null,
|
||||
shared: t.shared === 'true' || t.shared === true,
|
||||
created: t.created ?? null,
|
||||
lastModified: t.lastModified ?? null,
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
templates,
|
||||
totalSetSize: Number(data.totalSetSize) || 0,
|
||||
resultSetSize: Number(data.resultSetSize) || templates.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
templates: TEMPLATES_ARRAY_OUTPUT,
|
||||
totalSetSize: { type: 'number', description: 'Total number of matching templates' },
|
||||
resultSetSize: { type: 'number', description: 'Number of templates returned in this response' },
|
||||
},
|
||||
}
|
||||
119
apps/sim/tools/docusign/send_envelope.ts
Normal file
119
apps/sim/tools/docusign/send_envelope.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import type {
|
||||
DocuSignSendEnvelopeParams,
|
||||
DocuSignSendEnvelopeResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignSendEnvelopeTool: ToolConfig<
|
||||
DocuSignSendEnvelopeParams,
|
||||
DocuSignSendEnvelopeResponse
|
||||
> = {
|
||||
id: 'docusign_send_envelope',
|
||||
name: 'Send DocuSign Envelope',
|
||||
description: 'Create and send a DocuSign envelope with a document for e-signature',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
emailSubject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email subject for the envelope',
|
||||
},
|
||||
emailBody: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email body message',
|
||||
},
|
||||
signerEmail: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address of the signer',
|
||||
},
|
||||
signerName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full name of the signer',
|
||||
},
|
||||
ccEmail: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address of carbon copy recipient',
|
||||
},
|
||||
ccName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Full name of carbon copy recipient',
|
||||
},
|
||||
file: {
|
||||
type: 'file',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Document file to send for signature',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Envelope status: "sent" to send immediately, "created" for draft (default: "sent")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'send_envelope',
|
||||
emailSubject: params.emailSubject,
|
||||
emailBody: params.emailBody,
|
||||
signerEmail: params.signerEmail,
|
||||
signerName: params.signerName,
|
||||
ccEmail: params.ccEmail,
|
||||
ccName: params.ccName,
|
||||
file: params.file,
|
||||
status: params.status,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to send envelope')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
envelopeId: data.envelopeId ?? null,
|
||||
status: data.status ?? null,
|
||||
statusDateTime: data.statusDateTime ?? null,
|
||||
uri: data.uri ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
envelopeId: { type: 'string', description: 'Created envelope ID' },
|
||||
status: { type: 'string', description: 'Envelope status' },
|
||||
statusDateTime: { type: 'string', description: 'Status change datetime', optional: true },
|
||||
uri: { type: 'string', description: 'Envelope URI', optional: true },
|
||||
},
|
||||
}
|
||||
259
apps/sim/tools/docusign/types.ts
Normal file
259
apps/sim/tools/docusign/types.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
/** Common envelope output properties */
|
||||
export const ENVELOPE_OUTPUT_PROPERTIES = {
|
||||
envelopeId: { type: 'string', description: 'Unique envelope identifier' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Envelope status (created, sent, delivered, completed, declined, voided)',
|
||||
},
|
||||
emailSubject: { type: 'string', description: 'Email subject line' },
|
||||
sentDateTime: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime when envelope was sent',
|
||||
optional: true,
|
||||
},
|
||||
completedDateTime: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime when envelope was completed',
|
||||
optional: true,
|
||||
},
|
||||
createdDateTime: { type: 'string', description: 'ISO 8601 datetime when envelope was created' },
|
||||
statusChangedDateTime: { type: 'string', description: 'ISO 8601 datetime of last status change' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const RECIPIENT_OUTPUT_PROPERTIES = {
|
||||
recipientId: { type: 'string', description: 'Recipient identifier' },
|
||||
name: { type: 'string', description: 'Recipient name' },
|
||||
email: { type: 'string', description: 'Recipient email address' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Recipient signing status (sent, delivered, completed, declined)',
|
||||
},
|
||||
signedDateTime: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime when recipient signed',
|
||||
optional: true,
|
||||
},
|
||||
deliveredDateTime: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime when delivered to recipient',
|
||||
optional: true,
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const TEMPLATE_OUTPUT_PROPERTIES = {
|
||||
templateId: { type: 'string', description: 'Template identifier' },
|
||||
name: { type: 'string', description: 'Template name' },
|
||||
description: { type: 'string', description: 'Template description', optional: true },
|
||||
shared: { type: 'boolean', description: 'Whether template is shared', optional: true },
|
||||
created: { type: 'string', description: 'ISO 8601 creation date' },
|
||||
lastModified: { type: 'string', description: 'ISO 8601 last modified date' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const ENVELOPE_OBJECT_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'DocuSign envelope',
|
||||
properties: ENVELOPE_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
export const ENVELOPES_ARRAY_OUTPUT: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'Array of DocuSign envelopes',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: ENVELOPE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
|
||||
export const RECIPIENT_OBJECT_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'DocuSign recipient',
|
||||
properties: RECIPIENT_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
export const RECIPIENTS_ARRAY_OUTPUT: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'Array of DocuSign recipients',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: RECIPIENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
|
||||
export const TEMPLATES_ARRAY_OUTPUT: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'Array of DocuSign templates',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: TEMPLATE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
|
||||
/** Params interfaces */
|
||||
export interface DocuSignSendEnvelopeParams {
|
||||
accessToken: string
|
||||
emailSubject: string
|
||||
emailBody?: string
|
||||
signerEmail: string
|
||||
signerName: string
|
||||
ccEmail?: string
|
||||
ccName?: string
|
||||
file?: unknown
|
||||
status?: string
|
||||
}
|
||||
|
||||
export interface DocuSignCreateFromTemplateParams {
|
||||
accessToken: string
|
||||
templateId: string
|
||||
emailSubject?: string
|
||||
emailBody?: string
|
||||
templateRoles: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export interface DocuSignGetEnvelopeParams {
|
||||
accessToken: string
|
||||
envelopeId: string
|
||||
}
|
||||
|
||||
export interface DocuSignListEnvelopesParams {
|
||||
accessToken: string
|
||||
fromDate?: string
|
||||
toDate?: string
|
||||
envelopeStatus?: string
|
||||
searchText?: string
|
||||
count?: string
|
||||
}
|
||||
|
||||
export interface DocuSignVoidEnvelopeParams {
|
||||
accessToken: string
|
||||
envelopeId: string
|
||||
voidedReason: string
|
||||
}
|
||||
|
||||
export interface DocuSignDownloadDocumentParams {
|
||||
accessToken: string
|
||||
envelopeId: string
|
||||
documentId?: string
|
||||
}
|
||||
|
||||
export interface DocuSignListTemplatesParams {
|
||||
accessToken: string
|
||||
searchText?: string
|
||||
count?: string
|
||||
}
|
||||
|
||||
export interface DocuSignListRecipientsParams {
|
||||
accessToken: string
|
||||
envelopeId: string
|
||||
}
|
||||
|
||||
/** Response interfaces */
|
||||
export interface DocuSignSendEnvelopeResponse extends ToolResponse {
|
||||
output: {
|
||||
envelopeId: string
|
||||
status: string
|
||||
statusDateTime: string | null
|
||||
uri: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignCreateFromTemplateResponse extends ToolResponse {
|
||||
output: {
|
||||
envelopeId: string
|
||||
status: string
|
||||
statusDateTime: string | null
|
||||
uri: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignGetEnvelopeResponse extends ToolResponse {
|
||||
output: {
|
||||
envelopeId: string
|
||||
status: string
|
||||
emailSubject: string | null
|
||||
sentDateTime: string | null
|
||||
completedDateTime: string | null
|
||||
createdDateTime: string | null
|
||||
statusChangedDateTime: string | null
|
||||
voidedReason: string | null
|
||||
signerCount: number
|
||||
documentCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignListEnvelopesResponse extends ToolResponse {
|
||||
output: {
|
||||
envelopes: Array<{
|
||||
envelopeId: string
|
||||
status: string
|
||||
emailSubject: string | null
|
||||
sentDateTime: string | null
|
||||
completedDateTime: string | null
|
||||
createdDateTime: string | null
|
||||
statusChangedDateTime: string | null
|
||||
}>
|
||||
totalSetSize: number
|
||||
resultSetSize: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignVoidEnvelopeResponse extends ToolResponse {
|
||||
output: {
|
||||
envelopeId: string
|
||||
status: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignDownloadDocumentResponse extends ToolResponse {
|
||||
output: {
|
||||
base64Content: string
|
||||
mimeType: string
|
||||
fileName: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignListTemplatesResponse extends ToolResponse {
|
||||
output: {
|
||||
templates: Array<{
|
||||
templateId: string
|
||||
name: string
|
||||
description: string | null
|
||||
shared: boolean
|
||||
created: string | null
|
||||
lastModified: string | null
|
||||
}>
|
||||
totalSetSize: number
|
||||
resultSetSize: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DocuSignListRecipientsResponse extends ToolResponse {
|
||||
output: {
|
||||
signers: Array<{
|
||||
recipientId: string
|
||||
name: string
|
||||
email: string
|
||||
status: string
|
||||
signedDateTime: string | null
|
||||
deliveredDateTime: string | null
|
||||
}>
|
||||
carbonCopies: Array<{
|
||||
recipientId: string
|
||||
name: string
|
||||
email: string
|
||||
status: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export type DocuSignResponse =
|
||||
| DocuSignSendEnvelopeResponse
|
||||
| DocuSignCreateFromTemplateResponse
|
||||
| DocuSignGetEnvelopeResponse
|
||||
| DocuSignListEnvelopesResponse
|
||||
| DocuSignVoidEnvelopeResponse
|
||||
| DocuSignDownloadDocumentResponse
|
||||
| DocuSignListTemplatesResponse
|
||||
| DocuSignListRecipientsResponse
|
||||
72
apps/sim/tools/docusign/void_envelope.ts
Normal file
72
apps/sim/tools/docusign/void_envelope.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type {
|
||||
DocuSignVoidEnvelopeParams,
|
||||
DocuSignVoidEnvelopeResponse,
|
||||
} from '@/tools/docusign/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const docusignVoidEnvelopeTool: ToolConfig<
|
||||
DocuSignVoidEnvelopeParams,
|
||||
DocuSignVoidEnvelopeResponse
|
||||
> = {
|
||||
id: 'docusign_void_envelope',
|
||||
name: 'Void DocuSign Envelope',
|
||||
description: 'Void (cancel) a sent DocuSign envelope that has not yet been completed',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'docusign',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'DocuSign OAuth access token',
|
||||
},
|
||||
envelopeId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The envelope ID to void',
|
||||
},
|
||||
voidedReason: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reason for voiding the envelope',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/docusign',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
accessToken: params.accessToken,
|
||||
operation: 'void_envelope',
|
||||
envelopeId: params.envelopeId,
|
||||
voidedReason: params.voidedReason,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (data.success === false) {
|
||||
throw new Error(data.error || 'Failed to void envelope')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
envelopeId: data.envelopeId ?? null,
|
||||
status: data.status ?? 'voided',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
envelopeId: { type: 'string', description: 'Voided envelope ID' },
|
||||
status: { type: 'string', description: 'Envelope status (voided)' },
|
||||
},
|
||||
}
|
||||
@@ -348,6 +348,16 @@ import {
|
||||
discordUpdateMemberTool,
|
||||
discordUpdateRoleTool,
|
||||
} from '@/tools/discord'
|
||||
import {
|
||||
docusignCreateFromTemplateTool,
|
||||
docusignDownloadDocumentTool,
|
||||
docusignGetEnvelopeTool,
|
||||
docusignListEnvelopesTool,
|
||||
docusignListRecipientsTool,
|
||||
docusignListTemplatesTool,
|
||||
docusignSendEnvelopeTool,
|
||||
docusignVoidEnvelopeTool,
|
||||
} from '@/tools/docusign'
|
||||
import {
|
||||
dropboxCopyTool,
|
||||
dropboxCreateFolderTool,
|
||||
@@ -3806,6 +3816,14 @@ export const tools: Record<string, ToolConfig> = {
|
||||
discord_execute_webhook: discordExecuteWebhookTool,
|
||||
discord_get_webhook: discordGetWebhookTool,
|
||||
discord_delete_webhook: discordDeleteWebhookTool,
|
||||
docusign_create_from_template: docusignCreateFromTemplateTool,
|
||||
docusign_download_document: docusignDownloadDocumentTool,
|
||||
docusign_get_envelope: docusignGetEnvelopeTool,
|
||||
docusign_list_envelopes: docusignListEnvelopesTool,
|
||||
docusign_list_recipients: docusignListRecipientsTool,
|
||||
docusign_list_templates: docusignListTemplatesTool,
|
||||
docusign_send_envelope: docusignSendEnvelopeTool,
|
||||
docusign_void_envelope: docusignVoidEnvelopeTool,
|
||||
datadog_submit_metrics: datadogSubmitMetricsTool,
|
||||
datadog_query_timeseries: datadogQueryTimeseriesTool,
|
||||
datadog_create_event: datadogCreateEventTool,
|
||||
|
||||
Reference in New Issue
Block a user