diff --git a/docs/TurboSign/API Bulk Signatures.md b/docs/TurboSign/API Bulk Signatures.md new file mode 100644 index 0000000..f6f044a --- /dev/null +++ b/docs/TurboSign/API Bulk Signatures.md @@ -0,0 +1,713 @@ +--- +title: TurboSign Bulk API Integration +sidebar_position: 4 +description: Send documents for signature at scale using TurboSign Bulk API. Process hundreds or thousands of signature requests in batches with comprehensive tracking and management capabilities. +keywords: + - turbosign bulk api + - bulk signature api + - batch signature sending + - mass document signing + - bulk e-signature + - batch processing api + - bulk upload api + - signature automation + - batch document workflow + - turbodocx bulk api + - bulk signature integration + - api batch processing + - mass signature collection + - enterprise signature api + - bulk sending api + - batch management api + - signature tracking api + - bulk document processing + - scalable signature api + - high-volume signatures +--- + +import ScriptLoader from '@site/src/components/ScriptLoader'; + +# TurboSign Bulk API Integration + +Send documents for electronic signature at scale. The TurboSign Bulk API allows you to process hundreds or thousands of signature requests in organized batches, with comprehensive tracking and management capabilities. + +![TurboSign Single-Step Workflow](/img/turbosign/api/bulk-api.jpg) + +## Overview + +The Bulk API is designed for high-volume signature collection scenarios. Instead of sending documents one at a time, you can create batches that process multiple signature requests efficiently. + +### What is Bulk Sending? + +Bulk sending allows you to send the **same document to multiple recipients** in a single batch operation. Each recipient gets their own personalized copy with customized signature fields and recipient information. + +**Common Use Cases:** + +- **Employment Contracts**: Send offer letters or contracts to multiple new hires +- **Client Agreements**: Distribute service agreements to hundreds of clients +- **Policy Updates**: Send updated terms or policies requiring acknowledgment +- **Event Registrations**: Collect signatures from event participants +- **Real Estate**: Send disclosure forms to multiple buyers or sellers +- **Insurance**: Distribute policy documents requiring signatures + +### Key Benefits + +- **Efficiency**: Create one batch instead of hundreds of individual requests +- **Cost Effective**: Batch processing optimizes credit usage +- **Tracking**: Monitor progress across all jobs in a batch +- **Management**: Cancel, retry, or review jobs as needed +- **Scalability**: Handle thousands of signature requests seamlessly +- **Organization**: Group related requests together logically + +## Key Concepts + +Understanding these core concepts will help you effectively use the Bulk API. + +### Batches + +A **batch** is a container for multiple document send operations. All documents in a batch share the same source file (PDF, template, or deliverable) but can have different recipients and field configurations. + +### Jobs + +A **job** represents a single document send operation within a batch. Each job corresponds to one set of recipients receiving the shared document. + +### Source Types + +The Bulk API supports four source types for your documents: + +| Source Type | Description | When to Use | +| --------------- | ------------------------------ | ---------------------------------- | +| `file` | Upload PDF directly | One-time documents, custom content | +| `deliverableId` | Reference existing deliverable | Documents generated from templates | +| `templateId` | Use template directly | Reusable document structures | +| `fileLink` | URL to document | Documents hosted externally | + +### Batch-Level vs Document-Level Configuration + +**Batch-Level:** + +- Source file (all jobs use same document) +- Default metadata (documentName, documentDescription) +- Default sender information + +**Document-Level:** + +- Recipients (unique per job) +- Signature fields (customized per job) +- Optional metadata overrides per job + +## Prerequisites + +Before using the Bulk API, ensure you have: + +- **API Access Token**: Bearer token for authentication +- **Organization ID**: Your organization identifier +- **Bulk Sending Enabled**: Feature must be enabled for your organization +- **Sufficient Credits**: Credits will be reserved when creating batches + +### Getting Your Credentials + +1. **Login to TurboDocx**: Visit [https://www.turbodocx.com](https://www.turbodocx.com) +2. **Navigate to Settings**: Access your organization settings +3. **API Keys Section**: Generate or retrieve your API access token +4. **Organization ID**: Copy your organization ID from the settings + +![TurboSign API Key](/img/turbosign/api/api-key.png) +![TurboSign Organization ID](/img/turbosign/api/org-id.png) + +## Authentication + +All TurboSign Bulk API requests require authentication using a Bearer token: + +```http +Authorization: Bearer YOUR_API_TOKEN +``` + +Additional required headers for all requests: + +```http +x-rapiddocx-org-id: YOUR_ORGANIZATION_ID +User-Agent: TurboDocx API Client +``` + +:::tip Authentication +These authentication headers are identical to the single-step API. If you're already using the single-step API, you can use the same credentials for bulk operations. +::: + +## API Endpoints + +The Bulk API provides four endpoints for complete batch management: + +| Endpoint | Method | Purpose | +| --------------------------------------- | ------ | --------------------------------- | +| `/turbosign/bulk/ingest` | POST | Create and ingest a new batch | +| `/turbosign/bulk/batches` | GET | List all batches for organization | +| `/turbosign/bulk/batch/:batchId/jobs` | GET | List jobs within a specific batch | +| `/turbosign/bulk/batch/:batchId/cancel` | POST | Cancel a batch and pending jobs | + +--- + +## Endpoint 1: Ingest Bulk Batch + +Create a new batch and ingest multiple document send operations. This is the primary endpoint for initiating bulk sending. + +### Endpoint + +```http +POST https://api.turbodocx.com/turbosign/bulk/ingest +``` + +### Headers + +```http +Content-Type: multipart/form-data +Authorization: Bearer YOUR_API_TOKEN +x-rapiddocx-org-id: YOUR_ORGANIZATION_ID +User-Agent: TurboDocx API Client +``` + +### Request Body (multipart/form-data) + +⚠️ **Important**: `documents` must be sent as a JSON string in form-data + +| Field | Type | Required | Description | +| ------------------- | ----------------- | ------------- | ----------------------------------------------------------------- | +| sourceType | String | **Yes** | Source type: `file`, `deliverableId`, `templateId`, or `fileLink` | +| file | File | Conditional\* | PDF file upload (required when sourceType is `file`) | +| sourceValue | String (UUID/URL) | Conditional\* | UUID or URL (required for deliverableId/templateId/fileLink) | +| batchName | String | **Yes** | Name for this batch (max 255 chars) | +| documentName | String | No | Default document name for all jobs (max 255 chars) | +| documentDescription | String | No | Default description for all jobs (max 1000 chars) | +| senderName | String | No | Name of sender (max 255 chars) | +| senderEmail | String (email) | No | Email of sender | +| documents | String (JSON) | **Yes** | JSON string array of document objects | + +\* **File Source**: Must provide exactly ONE of: + +- `file` (upload) when sourceType is `file` +- `sourceValue` (UUID) when sourceType is `deliverableId` or `templateId` +- `sourceValue` (URL) when sourceType is `fileLink` + +### Documents Array Format + +The `documents` parameter must be a JSON string containing an array of document objects: + +```json +[ + { + "recipients": [ + { + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1 + } + ], + "fields": [ + { + "recipientEmail": "john.smith@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "required": true + }, + { + "recipientEmail": "john.smith@company.com", + "type": "date", + "page": 1, + "x": 100, + "y": 300, + "width": 150, + "height": 30, + "required": true + } + ], + "documentName": "Employment Contract - John Smith", + "documentDescription": "Please review and sign your employment contract" + }, + { + "recipients": [ + { + "name": "Jane Doe", + "email": "jane.doe@company.com", + "signingOrder": 1 + } + ], + "fields": [ + { + "recipientEmail": "jane.doe@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "required": true + } + ], + "documentName": "Employment Contract - Jane Doe" + } +] +``` + +**Document Object Properties:** + +| Property | Type | Required | Description | +| ------------------- | ------ | -------- | ----------------------------------------------------------- | +| recipients | Array | **Yes** | Array of recipient objects (same format as single-step API) | +| fields | Array | **Yes** | Array of field objects (same format as single-step API) | +| documentName | String | No | Override batch-level document name for this job | +| documentDescription | String | No | Override batch-level description for this job | + +:::tip Field Types +All field types from the single-step API are supported: `signature`, `initial`, `date`, `full_name`, `first_name`, `last_name`, `title`, `company`, `email`, `text`, `checkbox`. See the [single-step API documentation](/docs/TurboSign/API%20Signatures) for details. +::: + +### Response (Success) + +```json +{ + "success": true, + "batchId": "550e8400-e29b-41d4-a716-446655440000", + "batchName": "Q4 Employment Contracts", + "totalJobs": 50, + "status": "pending", + "message": "Batch created successfully with 50 jobs. Processing will begin shortly." +} +``` + +### Response (Error) + +```json +{ + "error": "Validation failed for 3 documents", + "code": "BulkValidationFailed", + "data": { + "invalidDocuments": [ + { + "index": 0, + "errors": ["recipients[0].email is required"] + }, + { + "index": 5, + "errors": ["fields[0].recipientEmail does not match any recipient"] + } + ] + } +} +``` + +### Code Example + + + +--- + +## Endpoint 2: List All Batches + +Retrieve all batches for your organization with pagination and filtering capabilities. + +### Endpoint + +```http +GET https://api.turbodocx.com/turbosign/bulk/batches +``` + +### Headers + +```http +Authorization: Bearer YOUR_API_TOKEN +x-rapiddocx-org-id: YOUR_ORGANIZATION_ID +User-Agent: TurboDocx API Client +``` + +### Query Parameters + +| Parameter | Type | Required | Description | +| --------- | ------------------ | -------- | ----------------------------------------------------------------------------- | +| limit | Number | No | Number of batches to return (default: 20, max: 100) | +| offset | Number | No | Number of batches to skip (default: 0) | +| query | String | No | Search query to filter batches by name | +| status | String or String[] | No | Filter by status: `pending`, `processing`, `completed`, `failed`, `cancelled` | +| startDate | String (ISO 8601) | No | Filter batches created on or after this date | +| endDate | String (ISO 8601) | No | Filter batches created on or before this date | + +### Response + +```json +{ + "data": { + "batches": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "Q4 Employment Contracts", + "status": "completed", + "totalJobs": 50, + "succeededJobs": 48, + "failedJobs": 2, + "pendingJobs": 0, + "createdOn": "2025-01-15T10:30:00Z", + "updatedOn": "2025-01-15T14:25:00Z", + "metadata": {} + }, + { + "id": "660e8400-e29b-41d4-a716-446655440001", + "name": "Client Agreements - January 2025", + "status": "processing", + "totalJobs": 100, + "succeededJobs": 75, + "failedJobs": 5, + "pendingJobs": 20, + "createdOn": "2025-01-16T09:00:00Z", + "updatedOn": "2025-01-16T12:30:00Z", + "metadata": {} + } + ], + "totalRecords": 2 + } +} +``` + +### Code Example + + + +--- + +## Endpoint 3: List Jobs in Batch + +Retrieve all jobs within a specific batch with detailed status information. + +### Endpoint + +```http +GET https://api.turbodocx.com/turbosign/bulk/batch/:batchId/jobs +``` + +### Headers + +```http +Authorization: Bearer YOUR_API_TOKEN +x-rapiddocx-org-id: YOUR_ORGANIZATION_ID +User-Agent: TurboDocx API Client +``` + +### Path Parameters + +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | --------------- | +| batchId | String (UUID) | **Yes** | ID of the batch | + +### Query Parameters + +| Parameter | Type | Required | Description | +| --------- | ------------------ | -------- | ------------------------------------------------ | +| limit | Number | No | Number of jobs to return (default: 20, max: 100) | +| offset | Number | No | Number of jobs to skip (default: 0) | +| status | String or String[] | No | Filter by job status | +| query | String | No | Search query to filter jobs | + +### Response + +```json +{ + "data": { + "batchId": "550e8400-e29b-41d4-a716-446655440000", + "batchName": "Q4 Employment Contracts", + "batchStatus": "completed", + "jobs": [ + { + "id": "770e8400-e29b-41d4-a716-446655440000", + "batchId": "550e8400-e29b-41d4-a716-446655440000", + "documentId": "880e8400-e29b-41d4-a716-446655440000", + "documentName": "Employment Contract - John Smith", + "status": "SUCCEEDED", + "recipientEmails": ["john.smith@company.com"], + "attempts": 0, + "errorCode": null, + "errorMessage": null, + "createdOn": "2025-01-15T10:30:00Z", + "updatedOn": "2025-01-15T11:45:00Z", + "lastAttemptedAt": "2025-01-15T11:45:00Z" + }, + { + "id": "770e8400-e29b-41d4-a716-446655440001", + "batchId": "550e8400-e29b-41d4-a716-446655440000", + "documentId": null, + "documentName": "Employment Contract - Jane Doe", + "status": "FAILED", + "recipientEmails": ["jane.doe@company.com"], + "attempts": 1, + "errorCode": "INVALID_EMAIL", + "errorMessage": "Invalid recipient email address", + "createdOn": "2025-01-15T10:30:00Z", + "updatedOn": "2025-01-15T10:31:00Z", + "lastAttemptedAt": "2025-01-15T10:31:00Z" + } + ], + "totalJobs": 50, + "totalRecords": 50, + "succeededJobs": 48, + "failedJobs": 2, + "pendingJobs": 0 + } +} +``` + +### Code Example + + + +--- + +## Endpoint 4: Cancel Batch + +Cancel a batch and stop all pending jobs. Already completed jobs are not affected. + +### Endpoint + +```http +POST https://api.turbodocx.com/turbosign/bulk/batch/:batchId/cancel +``` + +### Headers + +```http +Authorization: Bearer YOUR_API_TOKEN +x-rapiddocx-org-id: YOUR_ORGANIZATION_ID +User-Agent: TurboDocx API Client +``` + +### Path Parameters + +| Parameter | Type | Required | Description | +| --------- | ------------- | -------- | ------------------------- | +| batchId | String (UUID) | **Yes** | ID of the batch to cancel | + +### Response + +```json +{ + "success": true, + "message": "Batch cancelled. 20 job(s) stopped, 20 credit(s) refunded.", + "batchId": "550e8400-e29b-41d4-a716-446655440000", + "cancelledJobs": 20, + "succeededJobs": 30, + "refundedCredits": 20, + "status": "cancelled" +} +``` + +### Code Example + + + +:::warning Cancellation Notice + +- Only pending and processing jobs will be cancelled +- Already succeeded jobs are not affected +- Credits are refunded only for cancelled jobs +- Cancellation cannot be undone + ::: + +--- + +## Best Practices + +### Batch Size + +**Recommended batch sizes:** + +- Small batches (10-50 jobs): Faster processing, easier to monitor +- Medium batches (50-200 jobs): Balanced efficiency +- Large batches (200-1000 jobs): Maximum efficiency for large-scale operations + +:::tip Batch Size Strategy +For first-time use, start with smaller batches (10-20 jobs) to validate your configuration. Once confident, scale up to larger batches. +::: + +### Error Handling + +**Always check for validation errors:** + +```javascript +if (response.data.code === "BulkValidationFailed") { + const errors = response.data.data.invalidDocuments; + errors.forEach((err) => { + console.log(`Document ${err.index}: ${err.errors.join(", ")}`); + }); +} +``` + +**Monitor job failures:** + +- Regularly check job status +- Review error messages for failed jobs +- Fix issues and retry with corrected data + +### Credit Management + +**Credits are reserved when batch is created:** + +- 1 credit per recipient per job +- Credits are consumed as jobs succeed +- Credits are refunded for failed or cancelled jobs + +**Example**: Batch with 50 jobs, 1 recipient each = 50 credits reserved + +### Performance Tips + +1. **Use templateId or deliverableId**: Faster than file uploads +2. **Batch similar documents**: Group documents with similar configurations +3. **Validate data before submission**: Prevent validation errors +4. **Use pagination**: When listing large result sets +5. **Implement webhooks**: For real-time status updates (see [Webhooks documentation](/docs/TurboSign/Webhooks)) + +### Data Validation + +**Before creating a batch, validate:** + +- All email addresses are valid +- Field positioning doesn't overlap +- RecipientEmail in fields matches actual recipients +- Required fields are present + +--- + +## Error Codes + +Common error codes you may encounter: + +| Code | Description | Solution | +| ---------------------- | ------------------------------------------- | --------------------------------------- | +| `FileUploadRequired` | No file provided when sourceType is 'file' | Include file in form-data | +| `SourceValueRequired` | No sourceValue for deliverableId/templateId | Provide valid UUID | +| `InvalidSourceValue` | sourceValue provided with file upload | Remove sourceValue when using file | +| `InvalidDocumentsJSON` | Documents JSON is malformed | Validate JSON structure | +| `BulkValidationFailed` | One or more documents failed validation | Check data.invalidDocuments for details | +| `DeliverableNotFound` | deliverableId doesn't exist | Verify deliverable exists | +| `TemplateNotFound` | templateId doesn't exist | Verify template exists | +| `BatchNotFound` | batchId doesn't exist | Check batch ID | +| `BatchNotCancellable` | Batch already completed | Cannot cancel completed batches | +| `InsufficientCredits` | Not enough credits for batch | Add credits to organization | + +--- + +## Limits and Quotas + +### Batch Limits + +| Limit | Value | +| -------------------------- | -------------- | +| Maximum jobs per batch | 1,000 | +| Maximum document size | 25 MB | +| Maximum recipients per job | 50 | +| Maximum fields per job | 100 | +| Maximum batch name length | 255 characters | + +### Rate Limits + +| Endpoint | Rate Limit | +| ------------ | ------------------ | +| Ingest batch | 10 requests/minute | +| List batches | 60 requests/minute | +| List jobs | 60 requests/minute | +| Cancel batch | 10 requests/minute | + +:::warning Rate Limiting +If you exceed rate limits, you'll receive a `429 Too Many Requests` response. Implement exponential backoff in your retry logic. +::: + +### Credit Consumption + +- **1 credit per recipient**: Each recipient in each job consumes 1 credit +- **Reserved on creation**: Credits are reserved when batch is created +- **Consumed on success**: Credits are consumed when job succeeds +- **Refunded on failure/cancellation**: Credits are refunded for failed or cancelled jobs + +**Example Calculations:** + +- 100 jobs × 1 recipient = 100 credits +- 50 jobs × 2 recipients = 100 credits +- 200 jobs × 1 recipient = 200 credits + +--- + +## Troubleshooting + +### Batch Stuck in "Processing" + +**Possible causes:** + +- High system load (normal during peak times) +- Large batch size +- Complex document processing + +**Solutions:** + +- Wait for processing to complete (usually < 30 minutes) +- Check job status to see progress +- Contact support if stuck for > 1 hour + +### Jobs Failing with "Invalid Email" + +**Cause**: Email validation failed + +**Solutions:** + +- Verify email format +- Check for typos +- Ensure no special characters in local part + +### Credits Not Refunded + +**Cause**: Jobs succeeded before cancellation + +**Solution**: Credits are only refunded for cancelled jobs. Already succeeded jobs consume credits. + +### "DeliverableNotFound" Error + +**Possible causes:** + +- Wrong organization ID +- Deliverable deleted +- Incorrect deliverable ID + +**Solutions:** + +- Verify deliverable exists in your organization +- Check organization ID matches +- Generate new deliverable if needed + +--- + +## Next Steps + +Now that you understand the Bulk API, you might want to: + +- **Set up webhooks**: Get real-time notifications for batch and job status changes. See [Webhooks documentation](/docs/TurboSign/Webhooks) +- **Explore single-step API**: For individual document sending. See [Single-Step API documentation](/docs/TurboSign/API%20Signatures) +- **Review field types**: Learn about all available signature field types +- **Integrate with your app**: Build bulk sending into your application workflow + +--- + +_Have questions? Contact our support team at support@turbodocx.com_ diff --git a/docs/TurboSign/API Signatures.md b/docs/TurboSign/API Signatures.md index 0426deb..b73de02 100644 --- a/docs/TurboSign/API Signatures.md +++ b/docs/TurboSign/API Signatures.md @@ -1,12 +1,15 @@ --- title: TurboSign API Integration sidebar_position: 3 -description: Complete guide for integrating TurboSign API to upload documents, add recipients, and prepare documents for electronic signatures. Learn the 3-step process with detailed examples and code samples. +description: Complete guide for integrating TurboSign API using single-step document preparation. Send documents for electronic signatures in one API call with our simplified workflow. keywords: - turbosign api + - single-step signature api - document upload api - electronic signature api - - recipients api + - multipart form data + - prepare for review + - prepare for signing - signature preparation - api integration - document signing workflow @@ -35,26 +38,31 @@ import ScriptLoader from '@site/src/components/ScriptLoader'; # TurboSign API Integration -This comprehensive guide walks you through the TurboSign API integration process. Learn how to programmatically upload documents, add recipients, and prepare documents for electronic signatures using our RESTful API. +This comprehensive guide walks you through the TurboSign single-step API integration. Learn how to programmatically upload documents, configure recipients, set up signature fields, and send documents for electronic signatures using a single, streamlined API call. ![TurboSign API Integration Overview](/img/turbosign/api/api-illustration.png) ## Overview -The TurboSign API follows a simple 3-step process to prepare documents for electronic signatures: +The TurboSign API provides a simplified single-step process to prepare documents for electronic signatures. Instead of multiple API calls, you can now accomplish everything in one request. -1. **Upload Document** - Upload your PDF document to TurboSign -2. **Add Recipients** - Configure who needs to sign and their signing order -3. **Prepare for Signing** - Set up signature fields and send for signing +### Two Endpoint Options -![TurboSign API Integration Overview](/img/turbosign/api/steps.png) +TurboSign offers two single-step endpoints to fit different workflows: + +1. **Prepare for Review** - Upload and get preview URL (no emails sent) +2. **Prepare for Signing** - Upload and send immediately (emails sent) + +![TurboSign Single-Step Workflow](/img/turbosign/api/types.svg) ### Key Features -- **RESTful API**: Standard HTTP methods with JSON payloads +- **Single API Call**: Upload document, add recipients, and configure fields in one request +- **RESTful API**: Standard HTTP methods with multipart/form-data - **Bearer Token Authentication**: Secure API access using JWT tokens - **Multiple Recipients**: Support for multiple signers with custom signing order - **Flexible Field Placement**: Position signature fields using anchors or coordinates +- **Multiple File Sources**: Upload file, or reference deliverableId, templateId, or fileLink - **Real-time Status Updates**: Track document status throughout the signing process - **Webhook Integration**: Receive notifications when signing is complete @@ -64,58 +72,48 @@ Don't want to read all the details? Here's what you need to know: ### Available Field Types -| Type | Description | Use Case | -| ----------- | -------------------------- | ------------------------------------------------- | -| `signature` | Electronic signature field | Legal signatures | -| `initial` | Initial field | Document initials, paragraph acknowledgments | -| `date` | Date picker field | Signing date, agreement date | -| `full_name` | Full name field | Automatically fills signer's complete name | -| `first_name`| First name field | Automatically fills signer's first name | -| `last_name` | Last name field | Automatically fills signer's last name | -| `title` | Title/job title field | Professional title or position | -| `company` | Company name field | Organization or company name | -| `email` | Email address field | Signer's email address | -| `text` | Generic text input field | Custom text, notes, or any other text input | - -### Complete 3-Step Workflow +| Type | Description | Use Case | +| ------------ | -------------------------- | -------------------------------------------- | +| `signature` | Electronic signature field | Legal signatures | +| `initial` | Initial field | Document initials, paragraph acknowledgments | +| `date` | Date picker field | Signing date, agreement date | +| `full_name` | Full name field | Automatically fills signer's complete name | +| `first_name` | First name field | Automatically fills signer's first name | +| `last_name` | Last name field | Automatically fills signer's last name | +| `title` | Title/job title field | Professional title or position | +| `company` | Company name field | Organization or company name | +| `email` | Email address field | Signer's email address | +| `text` | Generic text input field | Custom text, notes, or any other text input | +| `checkbox` | Checkbox field | Acknowledgments, consent, agreements | + +### Quick Start: Prepare for Signing (Most Common) + +Use this endpoint to send documents immediately for signing: -### Quick Coordinate Example +### Alternative: Prepare for Review -If you prefer using exact coordinates instead of text anchors: +Use this endpoint when you need a preview URL to verify field placement: -```json -// Step 3 payload using coordinates instead of templates -[ - { - "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "type": "signature", - "page": 1, - "x": 100, - "y": 200, - "width": 200, - "height": 80, - "pageWidth": 612, - "pageHeight": 792 - }, - { - "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "type": "date", - "page": 1, - "x": 350, - "y": 300, - "width": 150, - "height": 30, - "pageWidth": 612, - "pageHeight": 792 - } -] -``` + + +### Quick Comparison + +| Feature | prepare-for-review | prepare-for-signing | +| -------------------- | ------------------------------ | ------------------------- | +| Sends emails? | ❌ No | ✅ Yes | +| Returns preview URL? | ✅ Yes | ❌ No | +| Final status | REVIEW_READY | UNDER_REVIEW | +| Use when | Need to verify field placement | Ready to send immediately | Now that you've seen the whole thing, let's dive into the details... @@ -134,8 +132,8 @@ Before you begin, ensure you have: 3. **API Keys Section**: Generate or retrieve your API access token 4. **Organization ID**: Copy your organization ID from the settings -![TurboSign API Integration Overview](/img/turbosign/api/api-key.png) -![TurboSign API Integration Overview](/img/turbosign/api/org-id.png) +![TurboSign API Key](/img/turbosign/api/api-key.png) +![TurboSign Organization ID](/img/turbosign/api/org-id.png) ## Authentication @@ -152,14 +150,40 @@ x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx API Client ``` -## Step 1: Upload Document +## Choosing Your Endpoint + +TurboSign offers two single-step endpoints to fit different workflows. Choose the one that best matches your use case. -The first step is to upload your PDF document to TurboSign. This creates a new document record and returns a document ID that you'll use in subsequent steps. +### When to Use prepare-for-review + +✅ **Use this endpoint when you want to:** + +- Verify field placement before sending to recipients +- Get a preview URL to review the document in TurboSign's interface +- Manually trigger email sending after verifying field placement +- Ensure correct field positioning before recipients receive emails + +**Workflow**: Upload → Get preview URL → Review in browser → Manually send when ready + +### When to Use prepare-for-signing + +✅ **Use this endpoint when you want to:** + +- Send documents immediately without preview step +- Automate the entire signature process end-to-end +- Use with verified templates or confident field positioning +- Skip manual review and send directly to recipients + +**Workflow**: Upload → Emails sent automatically → Recipients sign + +## Endpoint 1: Prepare for Review + +Creates a signature request and returns a preview URL. No emails are sent to recipients. ### Endpoint ```http -POST https://www.turbodocx.com/turbosign/documents/upload +POST https://api.turbodocx.com/turbosign/single/prepare-for-review ``` ### Headers @@ -171,379 +195,628 @@ x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx API Client ``` -### Request Body (Form Data) +### Request Body (multipart/form-data) + +⚠️ **Important**: Recipients and fields must be sent as JSON strings in form-data + +| Field | Type | Required | Description | +| ------------------- | -------------- | ------------- | ------------------------------------------ | +| file | File | Conditional\* | PDF, DOCX, or PPTX file to upload | +| deliverableId | String (UUID) | Conditional\* | Reference to existing deliverable | +| templateId | String (UUID) | Conditional\* | Reference to existing template | +| fileLink | String (URL) | Conditional\* | URL to download file from | +| documentName | String | No | Document name in TurboSign (max 255 chars) | +| documentDescription | String | No | Document description (max 1000 chars) | +| recipients | String (JSON) | **Yes** | JSON string array of recipient objects | +| fields | String (JSON) | **Yes** | JSON string array of field objects | +| senderName | String | No | Name of sender (max 255 chars) | +| senderEmail | String (email) | No | Email of sender | +| ccEmails | String (JSON) | No | JSON string array of CC email addresses | + +\* **File Source**: Must provide exactly ONE of: file, deliverableId, templateId, or fileLink + +### Recipients JSON Format + +Recipients must be stringified before adding to form-data: ```javascript -{ - "name": "Contract Agreement", - "file": [PDF_FILE_BINARY], - // Optional: triggerMeta for advanced configurations - // "triggerMeta": "{\"url\": \"callback_url\"}" -} +const recipients = JSON.stringify([ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1, + metadata: { + color: "hsl(200, 75%, 50%)", + lightColor: "hsl(200, 75%, 93%)", + }, + }, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2, + metadata: { + color: "hsl(270, 75%, 50%)", + lightColor: "hsl(270, 75%, 93%)", + }, + }, +]); +formData.append("recipients", recipients); +``` + +### Fields JSON Format + +Fields reference recipients by **email** (not recipientId) and must be stringified: + +#### Template-based (recommended): + +```javascript +const fields = JSON.stringify([ + { + recipientEmail: "john.smith@company.com", + type: "signature", + template: { + anchor: "{Signature1}", + placement: "replace", + size: { width: 200, height: 80 }, + offset: { x: 0, y: 0 }, + }, + required: true, + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + template: { + anchor: "{Date1}", + placement: "replace", + size: { width: 150, height: 30 }, + }, + required: true, + }, +]); +formData.append("fields", fields); +``` + +#### Coordinate-based: + +```javascript +const fields = JSON.stringify([ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + pageWidth: 612, + pageHeight: 792, + required: true, + }, +]); +formData.append("fields", fields); ``` ### Response ```json { - "data": { - "id": "4a20eca5-7944-430c-97d5-fcce4be24296", - "name": "Contract Agreement", - "description": "", - "status": "draft", - "createdOn": "2025-09-17T13:24:57.083Z" - } + "success": true, + "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", + "status": "REVIEW_READY", + "previewUrl": "https://www.turbodocx.com/sign/preview/abc123...", + "recipients": [ + { + "id": "5f673f37-9912-4e72-85aa-8f3649760f6b", + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1, + "metadata": { + "color": "hsl(200, 75%, 50%)", + "lightColor": "hsl(200, 75%, 93%)" + } + } + ], + "message": "Document prepared for review. Use the preview URL to review and assign fields." } ``` ### Response Fields -| Field | Type | Description | -| ------------------ | ------ | ----------------------------------------------------- | -| `data.id` | string | Unique document identifier (save this for next steps) | -| `data.name` | string | Document name as provided | -| `data.description` | string | Document description (empty by default) | -| `data.status` | string | Document status (`draft` after upload) | -| `data.createdOn` | string | ISO 8601 timestamp of document creation | - - +| Field | Type | Description | +| ---------- | ------------- | ---------------------------------------------- | +| success | Boolean | Request success status | +| documentId | String (UUID) | Unique document identifier - save for tracking | +| status | String | Document status (REVIEW_READY) | +| previewUrl | String (URL) | URL to preview and verify document | +| recipients | Array | Array of recipient objects with generated IDs | +| message | String | Human-readable success message | ### Code Examples - -## Step 2: Add Recipients +### Next Steps After Review + +Once you've reviewed the document via the preview URL click "Send for Signing" button on the preview page to send emails to recipients -After uploading your document, add the recipients who need to sign. You can specify multiple recipients with their signing order and customize their signature appearance. +## Endpoint 2: Prepare for Signing + +Creates a signature request and immediately sends emails to recipients. Use this for production workflows when you're confident in your field positioning. ### Endpoint ```http -POST https://www.turbodocx.com/turbosign/documents/{documentId}/update-with-recipients +POST https://api.turbodocx.com/turbosign/single/prepare-for-signing ``` ### Headers ```http -Content-Type: application/json +Content-Type: multipart/form-data Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx API Client ``` -### Request Body +### Request Body (multipart/form-data) -```json -{ - "document": { - "name": "Contract Agreement - Updated", - "description": "This document requires electronic signatures from both parties. Please review all content carefully before signing." - }, - "recipients": [ - { - "name": "John Smith", - "email": "john.smith@company.com", - "signingOrder": 1, - "metadata": { - "color": "hsl(200, 75%, 50%)", - "lightColor": "hsl(200, 75%, 93%)" - }, - "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296" - }, - { - "name": "Jane Doe", - "email": "jane.doe@partner.com", - "signingOrder": 2, - "metadata": { - "color": "hsl(270, 75%, 50%)", - "lightColor": "hsl(270, 75%, 93%)" - }, - "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296" - } - ] -} -``` +The request format is **identical** to prepare-for-review. See the "Endpoint 1: Prepare for Review" section above for detailed field documentation. ### Response ```json { - "data": { - "document": { - "id": "4a20eca5-7944-430c-97d5-fcce4be24296", - "name": "Contract Agreement - Updated", - "description": "This document requires electronic signatures from both parties. Please review all content carefully before signing.", - "status": "setup_complete", - "updatedOn": "2025-09-17T13:26:10.000Z" - }, - "recipients": [ - { - "id": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "name": "John Smith", - "email": "john.smith@company.com", - "signingOrder": 1, - "metadata": { - "color": "hsl(200, 75%, 50%)", - "lightColor": "hsl(200, 75%, 93%)" - } - }, - { - "id": "a8b9c1d2-3456-7890-abcd-ef1234567890", - "name": "Jane Doe", - "email": "jane.doe@partner.com", - "signingOrder": 2, - "metadata": { - "color": "hsl(270, 75%, 50%)", - "lightColor": "hsl(270, 75%, 93%)" - } - } - ] - } + "success": true, + "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", + "message": "Document sent for signing. Emails are being sent to recipients." } ``` -### Request Fields +### Response Fields -| Field | Type | Required | Description | -| ---------------------------------- | ------ | -------- | --------------------------------------------------- | -| `document.name` | string | Yes | Updated document name | -| `document.description` | string | No | Document description for recipients | -| `recipients[].name` | string | Yes | Full name of the signer | -| `recipients[].email` | string | Yes | Email address for signing notifications | -| `recipients[].signingOrder` | number | Yes | Order in which recipients should sign (1, 2, 3...) | -| `recipients[].metadata.color` | string | No | Primary color for this recipient's signature fields | -| `recipients[].metadata.lightColor` | string | No | Light color variant for highlights | -| `recipients[].documentId` | string | Yes | Document ID from Step 1 | +| Field | Type | Description | +| ---------- | ------------- | ---------------------------------------------- | +| success | Boolean | Request success status | +| documentId | String (UUID) | Unique document identifier - save for tracking | +| message | String | Human-readable success message | - +⚠️ **Note**: This endpoint returns immediately after creating the document. Email sending happens asynchronously in the background. Use webhooks to receive notification when the document is fully signed. ### Code Examples - -## Step 3: Prepare for Signing +## Recipients Reference -The final step configures signature fields and sends the document to recipients for signing. You can position fields using text anchors or absolute coordinates. +### Recipient Properties -### Endpoint +Each recipient object in the `recipients` array should contain the following properties: -```http -POST https://www.turbodocx.com/turbosign/documents/{documentId}/prepare-for-signing -``` +| Property | Type | Required | Description | +| ------------ | -------------- | -------- | ---------------------------------------------------------- | +| name | String | Yes | Full name of the recipient/signer | +| email | String (email) | Yes | Email address of the recipient (must be unique) | +| signingOrder | Number | Yes | Order in which recipient should sign (starts at 1) | +| metadata | Object | No | Optional metadata for UI customization (color, lightColor) | -### Headers +### Metadata Object (Optional) -```http -Content-Type: application/json -Authorization: Bearer YOUR_API_TOKEN -x-rapiddocx-org-id: YOUR_ORGANIZATION_ID -User-Agent: TurboDocx API Client -``` +The `metadata` object allows you to customize the recipient's UI appearance: -### Request Body +| Property | Type | Description | Example | +| ---------- | ------ | -------------------------------------------------- | ---------------------- | +| color | String | Primary color for recipient in HSL format | `"hsl(200, 75%, 50%)"` | +| lightColor | String | Light background color for recipient in HSL format | `"hsl(200, 75%, 93%)"` | + +### Example Recipients Array ```json [ { - "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "type": "signature", - "template": { - "anchor": "{Signature1}", - "placement": "replace", - "size": { "width": 200, "height": 80 }, - "offset": { "x": 0, "y": 0 }, - "caseSensitive": true, - "useRegex": false - }, - "defaultValue": "", - "required": true + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1 }, { - "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "type": "date", - "template": { - "anchor": "{Date1}", - "placement": "replace", - "size": { "width": 150, "height": 30 }, - "offset": { "x": 0, "y": 0 }, - "caseSensitive": true, - "useRegex": false - }, - "defaultValue": "", - "required": true - }, - { - "recipientId": "a8b9c1d2-3456-7890-abcd-ef1234567890", - "type": "signature", - "template": { - "anchor": "{Signature2}", - "placement": "replace", - "size": { "width": 200, "height": 80 }, - "offset": { "x": 0, "y": 0 }, - "caseSensitive": true, - "useRegex": false - }, - "defaultValue": "", - "required": true - }, + "name": "Jane Doe", + "email": "jane.doe@partner.com", + "signingOrder": 2 + } +] +``` + +### With Optional Metadata + +```json +[ { - "recipientId": "a8b9c1d2-3456-7890-abcd-ef1234567890", - "type": "text", - "template": { - "anchor": "{Title2}", - "placement": "replace", - "size": { "width": 200, "height": 30 }, - "offset": { "x": 0, "y": 0 }, - "caseSensitive": true, - "useRegex": false - }, - "defaultValue": "Business Partner", - "required": false + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1, + "metadata": { + "color": "hsl(200, 75%, 50%)", + "lightColor": "hsl(200, 75%, 93%)" + } } ] ``` -### Response +## Field Types Reference + +### Complete Field Type List + +| Type | Description | Auto-filled | Use Case | +| ------------ | -------------------------- | ----------- | -------------------------------------------- | +| `signature` | Electronic signature field | No | Legal signatures, agreements | +| `initial` | Initial field | No | Document initials, paragraph acknowledgments | +| `date` | Date picker field | No | Signing date, agreement date | +| `full_name` | Full name field | Yes | Automatically fills signer's complete name | +| `first_name` | First name field | Yes | Automatically fills signer's first name | +| `last_name` | Last name field | Yes | Automatically fills signer's last name | +| `title` | Title/job title field | No | Professional title or position | +| `company` | Company name field | No | Organization or company name | +| `email` | Email address field | Yes | Signer's email address | +| `text` | Generic text input field | No | Custom text, notes, or any other text input | +| `checkbox` | Checkbox field | No | Acknowledgments, consent, agreements | + +### Field Configuration Properties + +#### Common Properties (All Field Types) + +| Property | Type | Required | Description | +| --------------- | ------- | -------- | -------------------------------------------------------------- | +| recipientEmail | String | Yes | Email address of recipient (matches email in recipients array) | +| type | String | Yes | Field type (see table above) | +| required | Boolean | No | Whether field must be completed (default: true) | +| defaultValue | String | No | Pre-filled value for the field | +| isReadonly | Boolean | No | Makes field non-editable (for prefilled values) | +| backgroundColor | String | No | Custom background color (hex or rgba) | + +#### Template-based Properties + +| Property | Type | Required | Description | +| ---------------------- | ------- | -------- | --------------------------------------------------------------- | +| template.anchor | String | Yes | Text anchor to find in document (e.g., "{Signature1}") | +| template.placement | String | Yes | How to place field: "replace", "before", "after" | +| template.size | Object | Yes | Field dimensions: { width: number, height: number } | +| template.offset | Object | No | Position offset: { x: number, y: number } (default: {x:0, y:0}) | +| template.caseSensitive | Boolean | No | Whether anchor search is case-sensitive (default: true) | +| template.useRegex | Boolean | No | Whether to treat anchor as regex pattern (default: false) | + +#### Coordinate-based Properties + +| Property | Type | Required | Description | +| ---------- | ------ | -------- | ------------------------------------------------------------------ | +| page | Number | Yes | Page number (starts at 1) | +| x | Number | Yes | Horizontal position from left edge (pixels) | +| y | Number | Yes | Vertical position from top edge (pixels) | +| width | Number | Yes | Field width in pixels | +| height | Number | Yes | Field height in pixels | +| pageWidth | Number | No | Total page width in pixels (optional, for responsive positioning) | +| pageHeight | Number | No | Total page height in pixels (optional, for responsive positioning) | + +### Field Type Special Behaviors + +**signature & initial** + +- Draws a signature pad for user input +- Can be text-based or drawn +- Cryptographically signed and hashed for legal validity + +**date** + +- Shows date picker interface +- Format: MM/DD/YYYY (US) or DD/MM/YYYY (configurable) +- Can set defaultValue to "today" for auto-population + +**full_name, first_name, last_name, email** + +- Auto-populated from recipient profile +- Can be overridden by recipient if needed +- Useful for legal compliance and form filling + +**text** + +- Single-line text input by default +- Supports defaultValue for prefilled content +- Use for titles, company names, custom fields + +**checkbox** + +- Boolean true/false value +- Useful for acknowledgments and consent +- Can have label text next to checkbox + +## Field Positioning Methods + +TurboSign supports two methods for positioning signature fields in your documents. + +### Method 1: Template-based Positioning (Recommended) + +Uses text anchors in your PDF as placeholders. TurboSign searches for these anchors and places fields accordingly. + +#### Advantages + +✅ Easy to update field positions (just edit the PDF) +✅ No need to measure exact coordinates +✅ Works across different page sizes +✅ More maintainable for non-technical users +✅ Handles document variations gracefully + +#### How it Works + +1. **Add anchor text to your PDF**: Place text like `{Signature1}`, `{Date1}`, `{Initial1}` where you want fields +2. **Configure fields with anchor references**: Tell TurboSign what to search for +3. **TurboSign finds and replaces**: Anchors are found and replaced with interactive fields + +#### Anchor Configuration Example + +```json +{ + "recipientEmail": "john.smith@company.com", + "type": "signature", + "template": { + "anchor": "{Signature1}", + "placement": "replace", + "size": { "width": 200, "height": 80 }, + "offset": { "x": 0, "y": 0 }, + "caseSensitive": true, + "useRegex": false + }, + "required": true +} +``` + +#### Placement Options + +- **replace**: Removes the anchor text and places the field in its position +- **before**: Places field before the anchor text (anchor remains visible) +- **after**: Places field after the anchor text (anchor remains visible) + +#### Offset Usage + +Offset allows fine-tuning field position relative to the anchor: + +- `x`: Positive moves right, negative moves left (pixels) +- `y`: Positive moves down, negative moves up (pixels) ```json { - "success": true + "anchor": "{Signature1}", + "placement": "replace", + "size": { "width": 200, "height": 80 }, + "offset": { "x": 10, "y": -5 } // 10px right, 5px up from anchor } ``` -### Field Positioning Options +### Method 2: Coordinate-based Positioning -TurboSign supports two field positioning methods: +Uses exact pixel coordinates to position fields on specific pages. Best for precise control or when anchors aren't feasible. -1. **Template-based positioning** (recommended) - Uses text anchors in your PDF -2. **Coordinate-based positioning** - Uses exact pixel coordinates +#### Advantages -#### Template-based Field Configuration +✅ Pixel-perfect precision +✅ Works with PDFs that can't be edited +✅ Programmatically generated positions +✅ Useful for form-filling scenarios +✅ Consistent placement across documents -| Field | Type | Required | Description | -| ------------------------ | ------- | -------- | ------------------------------------------------------ | -| `recipientId` | string | Yes | Recipient ID from Step 2 | -| `type` | string | Yes | Field type - see Available Field Types in TLDR section | -| `template.anchor` | string | Yes | Text anchor to find in document (e.g., "{Signature1}") | -| `template.placement` | string | Yes | How to place field ("replace", "before", "after") | -| `template.size` | object | Yes | Field dimensions (width, height in pixels) | -| `template.offset` | object | No | Position offset from anchor (x, y in pixels) | -| `template.caseSensitive` | boolean | No | Whether anchor search is case-sensitive | -| `template.useRegex` | boolean | No | Whether to treat anchor as regex pattern | -| `defaultValue` | string | No | Pre-filled value for the field | -| `required` | boolean | No | Whether field must be completed | +#### How it Works -#### Coordinate-based Field Configuration +1. **Measure exact x,y coordinates** in your PDF (using PDF editor or viewer) +2. **Provide page number, coordinates, and dimensions** +3. **TurboSign places fields at exact positions** -For precise positioning when text anchors aren't suitable, use coordinate-based fields: +#### Coordinate Configuration Example ```json -[ - { - "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "type": "signature", - "page": 1, - "x": 100, - "y": 200, - "width": 200, - "height": 80, - "pageWidth": 612, - "pageHeight": 792 - }, - { - "recipientId": "5f673f37-9912-4e72-85aa-8f3649760f6b", - "type": "date", - "page": 1, - "x": 350, - "y": 200, - "width": 150, - "height": 30, - "pageWidth": 612, - "pageHeight": 792 - } -] +{ + "recipientEmail": "john.smith@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "pageWidth": 612, + "pageHeight": 792, + "required": true +} ``` -| Field | Type | Required | Description | -| ------------ | ------ | -------- | ----------------------------------------------- | -| `recipientId`| string | Yes | Recipient ID from Step 2 | -| `type` | string | Yes | Field type - see Available Field Types section | -| `page` | number | Yes | Page number (starts at 1) | -| `x` | number | Yes | Horizontal position from left edge (pixels) | -| `y` | number | Yes | Vertical position from top edge (pixels) | -| `width` | number | Yes | Field width in pixels | -| `height` | number | Yes | Field height in pixels | -| `pageWidth` | number | Yes | Total page width in pixels | -| `pageHeight` | number | Yes | Total page height in pixels | - -**Coordinate System Notes:** -- Origin (0,0) is at the top-left corner of the page -- Standard US Letter page size is 612 x 792 pixels (8.5" x 11" at 72 DPI) -- Fields cannot extend beyond page boundaries: `x + width ≤ pageWidth` and `y + height ≤ pageHeight` -- All coordinate values must be non-negative numbers - -**When to use coordinate-based positioning:** -- Precise pixel-perfect placement needed -- PDF doesn't contain suitable text anchors -- Programmatically generated field positions -- Converting from existing coordinate-based systems - - +#### Coordinate System Reference -### Code Examples +- **Origin (0,0)**: Top-left corner of the page +- **X-axis**: Increases from left to right +- **Y-axis**: Increases from top to bottom +- **Standard US Letter**: 612 x 792 pixels (8.5" x 11" at 72 DPI) +- **Standard A4**: 595 x 842 pixels (210mm x 297mm at 72 DPI) - +#### Coordinate Validation + +Fields must stay within page boundaries: + +- `x ≥ 0` +- `y ≥ 0` +- `x + width ≤ pageWidth` +- `y + height ≤ pageHeight` + +#### Measuring Coordinates + +**Adobe Acrobat Pro**: + +1. View → Show/Hide → Rulers & Grids → Rulers +2. Hover over location to see coordinates + +**Browser Developer Tools**: + +1. Open PDF in browser +2. Right-click → Inspect +3. Use element inspector to measure positions + +**PDF Editing Software**: + +- Use built-in coordinate display +- Draw rectangles to measure dimensions + +#### Quick Coordinate Example + +Position a signature field at bottom-right of a US Letter page: + +```json +{ + "recipientEmail": "john@example.com", + "type": "signature", + "page": 1, + "x": 362, // 612 - 250 = 362 (right aligned with 50px margin) + "y": 662, // 792 - 130 = 662 (bottom aligned with 50px margin) + "width": 200, + "height": 80, + "pageWidth": 612, + "pageHeight": 792 +} +``` ## Best Practices +### Workflow Selection + +**When You Need Field Verification**: + +- ✅ Use `prepare-for-review` to get preview URLs +- ✅ Verify field placement in browser before sending +- ✅ Manually trigger sending after review +- ✅ Useful for new document templates or complex field layouts + +**When Field Placement Is Verified**: + +- ✅ Use `prepare-for-signing` to send immediately +- ✅ Implement webhook handlers for completion notifications +- ✅ Use proper error handling and retry logic +- ✅ Monitor API rate limits +- ✅ Log all document IDs for tracking + +**General Tips**: + +- ✅ Use deliverableId or templateId to avoid repeated uploads +- ✅ Test with your own email addresses first +- ✅ Both endpoints are production-ready + ### Security -- **Never expose API tokens**: Store tokens securely in environment variables -- **Use HTTPS only**: All API calls must use HTTPS in production -- **Validate inputs**: Always validate recipient emails and document names +- **Never expose API tokens**: Store tokens securely in environment variables or secrets management +- **Use HTTPS only**: All API calls must use HTTPS in production (API enforces this) +- **Validate inputs**: Always validate recipient emails and document names before submission - **Implement rate limiting**: Respect API rate limits to avoid throttling +- **Rotate tokens regularly**: Generate new API tokens periodically +- **Use webhook signatures**: Verify webhook payloads using HMAC signatures +- **Sanitize user inputs**: Validate and sanitize all user-provided data ### Error Handling - **Check HTTP status codes**: Always verify response status before processing -- **Handle timeouts**: Implement retry logic for network failures +- **Handle timeouts**: Implement retry logic with exponential backoff for network failures - **Log API responses**: Keep detailed logs for debugging and monitoring - **Validate responses**: Check response structure before accessing data +- **Graceful degradation**: Have fallback behavior for API failures +- **User-friendly errors**: Display helpful error messages to end users ### Performance -- **Upload optimization**: Compress PDFs when possible to reduce upload time -- **Batch operations**: Group multiple recipients in single API calls -- **Async processing**: Use webhooks instead of polling for status updates -- **Connection pooling**: Reuse HTTP connections for multiple requests +**File Upload Optimization**: + +- Compress PDFs when possible (aim for <5MB) +- Use fileLink for files already in cloud storage (S3, GCS, etc.) +- Use deliverableId/templateId to reference existing documents +- Avoid uploading the same document multiple times + +**API Efficiency**: + +- Single-step endpoints reduce API calls from 3 to 1 (3x faster) +- Batch multiple documents in parallel requests when possible +- Use connection pooling for multiple requests +- Implement exponential backoff for retries +- Cache responses when appropriate + +**Network Optimization**: + +- Use CDN for document hosting when using fileLink +- Enable gzip compression for API requests +- Minimize payload sizes by only including required fields ### Document Preparation -- **Use text anchors**: Place anchor text like `{Signature1}` in your PDFs for precise field positioning -- **Consistent naming**: Use consistent anchor naming conventions across documents -- **Test coordinates**: Verify field positions with test documents before production -- **Document validation**: Ensure PDFs are not password-protected or corrupted +**Text Anchors (Template-based)**: + +- Use consistent anchor naming: `{FieldType}{Number}` (e.g., `{Signature1}`, `{Date1}`) +- Place anchors exactly where you want fields +- Use unique anchors (avoid duplicates) +- Test anchor placement with prepare-for-review first +- Document your anchor naming convention + +**Coordinate-based**: + +- Verify coordinates work across different PDF viewers +- Account for page margins and headers/footers +- Use standard page sizes when possible +- Test on actual page dimensions (don't assume) +- Validate boundaries before submission + +**Document Validation**: -### Coordinate-based Positioning Tips +- Ensure PDFs are not password-protected or corrupted +- Verify all pages are readable +- Test with actual documents before production +- Keep backup copies of source documents -- **Measure accurately**: Use PDF viewers with coordinate display to find exact positions -- **Account for margins**: Consider document margins when calculating field positions -- **Test on different devices**: Verify coordinates work across different PDF viewers -- **Use standard page sizes**: Stick to common page dimensions (612x792 for US Letter) -- **Validate boundaries**: Ensure fields don't extend beyond page edges -- **Consider scaling**: Be aware that different DPI settings may affect coordinates +### JSON String Formatting + +⚠️ **Critical**: Recipients and fields must be valid JSON strings when added to form-data. + +**Correct**: + +```javascript +const recipients = JSON.stringify([ + { name: "John", email: "john@example.com", signingOrder: 1 }, +]); +formData.append("recipients", recipients); +``` + +**Incorrect**: + +```javascript +// Don't send object/array directly! +formData.append("recipients", recipientsArray); // ❌ Wrong +formData.append("recipients", "[{...}]"); // ❌ Wrong (string literal, not stringified) +``` + +**Python Example**: + +```python +import json +recipients = json.dumps([ + {"name": "John", "email": "john@example.com", "signingOrder": 1} +]) +form_data['recipients'] = recipients +``` + +**C# Example**: + +```csharp +using System.Text.Json; +var recipients = JsonSerializer.Serialize(new[] { + new { name = "John", email = "john@example.com", signingOrder = 1 } +}); +formData.Add(new StringContent(recipients), "recipients"); +``` ## Error Handling & Troubleshooting @@ -555,61 +828,132 @@ For precise positioning when text anchors aren't suitable, use coordinate-based | `400` | Bad Request | Check request body format and required fields | | `401` | Unauthorized | Verify API token and headers | | `403` | Forbidden | Check organization ID and permissions | -| `404` | Not Found | Verify document ID and endpoint URLs | +| `404` | Not Found | Verify endpoint URLs are correct | | `422` | Unprocessable Entity | Validate field values and constraints | | `429` | Too Many Requests | Implement rate limiting and retry logic | | `500` | Internal Server Error | Contact support if persistent | ### Common Issues -#### Authentication Failures +#### JSON String Formatting Errors -**Symptoms**: 401 Unauthorized responses +**Symptoms**: 400 Bad Request with message "Invalid JSON in recipients/fields" **Solutions**: -- Verify API token is correct and not expired -- Check that `x-rapiddocx-org-id` header matches your organization -- Ensure Bearer token format: `Bearer YOUR_TOKEN` +- ✅ Verify JSON.stringify() or equivalent is used for recipients, fields, ccEmails +- ✅ Check JSON is valid using JSONLint or similar validator +- ✅ Ensure proper escaping of quotes in JSON strings +- ✅ Test with minimal example first (1 recipient, 1 field) -#### Document Upload Failures +**Example Error Response**: -**Symptoms**: Upload returns error or times out +```json +{ + "error": "Invalid JSON string in recipients field", + "code": "JSONParseError", + "details": "Unexpected token at position 45" +} +``` + +**Debug Steps**: + +1. Log the JSON string before sending +2. Validate JSON with online validator +3. Check for special characters or unescaped quotes +4. Test with hardcoded valid JSON first + +#### File Source Errors + +**Symptoms**: 400 Bad Request with message about file source **Solutions**: -- Verify PDF file is not corrupted or password-protected -- Check file size is under the maximum limit (typically 10MB) -- Ensure file is actually a PDF (check MIME type) -- Verify network connection and try again +- ✅ Provide exactly ONE of: file, deliverableId, templateId, fileId, fileLink +- ✅ Verify UUIDs are valid format (8-4-4-4-12 characters) +- ✅ Check file upload isn't corrupted or empty +- ✅ Ensure fileLink is accessible (not behind auth) -#### Recipient Configuration Issues +**Example Error Response**: -**Symptoms**: Recipients not receiving signing invitations +```json +{ + "error": "Must provide exactly one file source", + "code": "InvalidFileSource" +} +``` + +#### Recipients/Fields Mismatch + +**Symptoms**: 400 Bad Request about missing recipient or email mismatch + +**Solutions**: + +- ✅ Verify recipientEmail in fields matches email in recipients array **exactly** +- ✅ Check for typos in email addresses +- ✅ Ensure all fields reference valid recipients +- ✅ Email matching is case-sensitive + +**Example**: + +```javascript +// Recipients array +[{ email: "john.smith@company.com", ... }] + +// Fields array - must match exactly +[{ recipientEmail: "john.smith@company.com", ... }] // ✅ Correct +[{ recipientEmail: "John.Smith@company.com", ... }] // ❌ Wrong (case mismatch) +``` + +#### Authentication Failures + +**Symptoms**: 401 Unauthorized responses + +**Solutions**: + +- ✅ Verify API token is correct and not expired +- ✅ Check that `x-rapiddocx-org-id` header matches your organization +- ✅ Ensure Bearer token format: `Bearer YOUR_TOKEN` (with space) +- ✅ Confirm token has necessary permissions + +**Example Correct Headers**: + +```http +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +x-rapiddocx-org-id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 +``` + +#### Document Upload Failures + +**Symptoms**: Upload returns error or times out **Solutions**: -- Verify email addresses are valid and correctly formatted -- Check signing order numbers are sequential (1, 2, 3...) -- Ensure document ID from Step 1 is used correctly -- Verify recipient metadata format is correct +- ✅ Verify PDF file is not corrupted or password-protected +- ✅ Check file size is under maximum limit (typically 10MB) +- ✅ Ensure file is actually a PDF (check MIME type) +- ✅ Verify network connection and try again +- ✅ For fileLink, ensure URL is accessible #### Field Positioning Problems -**Symptoms**: Signature fields appear in wrong locations +**Symptoms**: Signature fields appear in wrong locations or not at all **Template-based Solutions**: -- Verify anchor text exists in the PDF document -- Check anchor text matches exactly (case-sensitive by default) -- Test with `caseSensitive: false` if having matching issues -- Use PDF coordinates as fallback if anchors don't work + +- ✅ Verify anchor text exists in the PDF document +- ✅ Check anchor text matches exactly (case-sensitive by default) +- ✅ Test with `caseSensitive: false` if having matching issues +- ✅ Try different placement options (replace, before, after) +- ✅ Use prepare-for-review to visually verify placement **Coordinate-based Solutions**: -- Verify page dimensions match your PDF's actual size -- Check that x,y coordinates are within page boundaries -- Ensure coordinates account for any PDF margins or headers -- Test with different page numbers if multi-page document -- Validate that `x + width ≤ pageWidth` and `y + height ≤ pageHeight` + +- ✅ Verify page dimensions match your PDF's actual size +- ✅ Check that x,y coordinates are within page boundaries +- ✅ Ensure coordinates account for any PDF margins or headers +- ✅ Test with different page numbers if multi-page document +- ✅ Validate that `x + width ≤ pageWidth` and `y + height ≤ pageHeight` #### Webhook Integration Issues @@ -617,26 +961,44 @@ For precise positioning when text anchors aren't suitable, use coordinate-based **Solutions**: -- Verify webhook URLs are accessible and return 200 OK -- Check webhook configuration in organization settings -- Review webhook delivery history for error details -- Test webhook endpoints with external tools +- ✅ Verify webhook URLs are accessible and return 200 OK +- ✅ Check webhook configuration in organization settings +- ✅ Review webhook delivery history for error details +- ✅ Test webhook endpoints with external tools (webhook.site, ngrok) +- ✅ Implement HMAC signature verification ### Debugging Tips -1. **Enable request logging**: Log all API requests and responses -2. **Test step by step**: Isolate issues by testing each step individually -3. **Use Postman**: Import examples and test manually before coding -4. **Check network**: Verify connectivity to `turbodocx.com` -5. **Validate JSON**: Ensure request bodies are valid JSON format - - +1. **Test with prepare-for-review first**: Visual confirmation before sending emails +2. **Use preview URLs**: Verify field placement and document appearance +3. **Check response documentId**: Save this for tracking and debugging +4. **Enable request logging**: Log all requests and responses with timestamps +5. **Test with minimal payloads**: Start simple (1 recipient, 1 field), add complexity incrementally +6. **Validate JSON before sending**: Use JSON validators to check format +7. **Use Postman/Insomnia**: Test manually before writing code +8. **Check API status page**: Verify TurboDocx services are operational +9. **Review error messages carefully**: Error responses include specific details +10. **Monitor rate limits**: Track API usage to avoid throttling + +### Example Debug Request + +```bash +# Test with curl to isolate issues +curl -X POST https://api.turbodocx.com/turbosign/single/prepare-for-review \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "x-rapiddocx-org-id: YOUR_ORG_ID" \ + -F "file=@document.pdf" \ + -F "documentName=Test Document" \ + -F 'recipients=[{"name":"Test User","email":"test@example.com","signingOrder":1}]' \ + -F 'fields=[{"recipientEmail":"test@example.com","type":"signature","page":1,"x":100,"y":200,"width":200,"height":80,"pageWidth":612,"pageHeight":792}]' \ + -v +``` ## Next Steps ### Webhooks - The Next Logical Step -Now that you've integrated the basic signing flow, the next step is setting up webhooks to receive real-time notifications when documents are signed. This eliminates the need for polling and provides instant updates about document status changes. +Now that you've integrated the single-step signing flow, the next step is setting up webhooks to receive real-time notifications when documents are signed. This eliminates the need for polling and provides instant updates about document status changes. 📖 **[Learn how to configure Webhooks →](/docs/TurboSign/Webhooks)** @@ -656,4 +1018,4 @@ Need help with your integration? --- -Ready to get started? Follow the step-by-step guide above to integrate TurboSign API into your application and start collecting electronic signatures programmatically! +Ready to get started? Follow the guide above to integrate TurboSign single-step API into your application and start collecting electronic signatures programmatically with a single API call! diff --git a/docs/TurboSign/Setting up TurboSign.md b/docs/TurboSign/Setting up TurboSign.md index 24e6cdf..d55c08c 100644 --- a/docs/TurboSign/Setting up TurboSign.md +++ b/docs/TurboSign/Setting up TurboSign.md @@ -199,6 +199,8 @@ For each recipient, you'll need: ![Add recipients interface showing all options](/img/turbosign/AddRecipients.png) +**CC Emails (Optional):** You can also add CC email addresses for people who should receive a copy of the document. These individuals will get the final signed copy when everyone has completed signing, but they won't need to sign the document themselves. +
:::tip Recipient Best Practices diff --git a/static/img/turbosign/api/bulk-api.jpg b/static/img/turbosign/api/bulk-api.jpg new file mode 100644 index 0000000..870cabc Binary files /dev/null and b/static/img/turbosign/api/bulk-api.jpg differ diff --git a/static/img/turbosign/api/types.svg b/static/img/turbosign/api/types.svg new file mode 100644 index 0000000..5486791 --- /dev/null +++ b/static/img/turbosign/api/types.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/static/scripts/turbosign/api/bulk/cancel-batch/nodejs/express.js b/static/scripts/turbosign/api/bulk/cancel-batch/nodejs/express.js new file mode 100644 index 0000000..d4edf5e --- /dev/null +++ b/static/scripts/turbosign/api/bulk/cancel-batch/nodejs/express.js @@ -0,0 +1,37 @@ +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; +const BATCH_ID = "YOUR_BATCH_ID"; // Replace with actual batch ID to cancel + +// Send POST request to cancel batch +const response = await fetch(`${BASE_URL}/turbosign/bulk/batch/${BATCH_ID}/cancel`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + 'Content-Type': 'application/json' + } +}); + +const result = await response.json(); + +if (result.success) { + console.log('✅ Batch cancelled successfully'); + console.log('Batch ID:', result.batchId); + console.log('Status:', result.status); + console.log('\n📊 Cancellation Summary:'); + console.log('Cancelled Jobs:', result.cancelledJobs); + console.log('Succeeded Jobs (already completed):', result.succeededJobs); + console.log('Refunded Credits:', result.refundedCredits); + console.log('\n💰 Credits have been refunded to your account for cancelled jobs'); + console.log('✅ Jobs that already succeeded remain completed'); +} else { + console.error('❌ Error:', result.error || result.message); + if (result.code) { + console.error('Error Code:', result.code); + } +} diff --git a/static/scripts/turbosign/api/bulk/cancel-batch/nodejs/fastify.js b/static/scripts/turbosign/api/bulk/cancel-batch/nodejs/fastify.js new file mode 100644 index 0000000..5cc197f --- /dev/null +++ b/static/scripts/turbosign/api/bulk/cancel-batch/nodejs/fastify.js @@ -0,0 +1,53 @@ +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; +const BATCH_ID = "YOUR_BATCH_ID"; // Replace with actual batch ID to cancel + +// Fastify route handler example +async function cancelBatch(request, reply) { + try { + // Send POST request to cancel batch + const response = await fetch(`${BASE_URL}/turbosign/bulk/batch/${BATCH_ID}/cancel`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + 'Content-Type': 'application/json' + } + }); + + const result = await response.json(); + + if (result.success) { + return reply.send({ + success: true, + batchId: result.batchId, + status: result.status, + message: result.message, + cancelledJobs: result.cancelledJobs, + succeededJobs: result.succeededJobs, + refundedCredits: result.refundedCredits + }); + } else { + return reply.code(400).send({ + success: false, + error: result.error || result.message, + code: result.code + }); + } + } catch (error) { + console.error('Error cancelling batch:', error); + return reply.code(500).send({ + success: false, + error: 'Internal server error', + message: error.message + }); + } +} + +// Export for use in Fastify app +module.exports = { cancelBatch }; diff --git a/static/scripts/turbosign/api/bulk/cancel-batch/python/fastapi.py b/static/scripts/turbosign/api/bulk/cancel-batch/python/fastapi.py new file mode 100644 index 0000000..5bd16db --- /dev/null +++ b/static/scripts/turbosign/api/bulk/cancel-batch/python/fastapi.py @@ -0,0 +1,71 @@ +import json +import requests +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" +BATCH_ID = "YOUR_BATCH_ID" # Replace with actual batch ID to cancel + +app = FastAPI() + +class CancelBatchResponse(BaseModel): + success: bool + batchId: str + status: str + message: str + cancelledJobs: int + succeededJobs: int + refundedCredits: int + +@app.post('/cancel-batch', response_model=CancelBatchResponse) +async def cancel_batch(): + try: + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + 'Content-Type': 'application/json' + } + + # Send POST request to cancel batch + response = requests.post( + f'{BASE_URL}/turbosign/bulk/batch/{BATCH_ID}/cancel', + headers=headers + ) + + result = response.json() + + if result.get('success'): + return CancelBatchResponse( + success=True, + batchId=result['batchId'], + status=result['status'], + message=result['message'], + cancelledJobs=result['cancelledJobs'], + succeededJobs=result['succeededJobs'], + refundedCredits=result['refundedCredits'] + ) + else: + raise HTTPException( + status_code=400, + detail={ + 'error': result.get('error', 'Failed to cancel batch'), + 'code': result.get('code') + } + ) + + except HTTPException: + raise + except Exception as error: + print(f'Error cancelling batch: {error}') + raise HTTPException( + status_code=500, + detail={ + 'error': 'Internal server error', + 'message': str(error) + } + ) diff --git a/static/scripts/turbosign/api/bulk/cancel-batch/python/flask.py b/static/scripts/turbosign/api/bulk/cancel-batch/python/flask.py new file mode 100644 index 0000000..72300de --- /dev/null +++ b/static/scripts/turbosign/api/bulk/cancel-batch/python/flask.py @@ -0,0 +1,58 @@ +import json +import requests +from flask import Flask, jsonify + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" +BATCH_ID = "YOUR_BATCH_ID" # Replace with actual batch ID to cancel + +app = Flask(__name__) + +@app.route('/cancel-batch', methods=['POST']) +def cancel_batch(): + try: + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + 'Content-Type': 'application/json' + } + + # Send POST request to cancel batch + response = requests.post( + f'{BASE_URL}/turbosign/bulk/batch/{BATCH_ID}/cancel', + headers=headers + ) + + result = response.json() + + if result.get('success'): + return jsonify({ + 'success': True, + 'batchId': result['batchId'], + 'status': result['status'], + 'message': result['message'], + 'cancelledJobs': result['cancelledJobs'], + 'succeededJobs': result['succeededJobs'], + 'refundedCredits': result['refundedCredits'] + }), 200 + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'Failed to cancel batch'), + 'code': result.get('code') + }), 400 + + except Exception as error: + print(f'Error cancelling batch: {error}') + return jsonify({ + 'success': False, + 'error': 'Internal server error', + 'message': str(error) + }), 500 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/scripts/turbosign/api/bulk/ingest/nodejs/express.js b/static/scripts/turbosign/api/bulk/ingest/nodejs/express.js new file mode 100644 index 0000000..68e1598 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/ingest/nodejs/express.js @@ -0,0 +1,121 @@ +const FormData = require('form-data'); +const fs = require('fs'); +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Prepare documents array - each document represents one signing job +const documents = [ + { + recipients: [ + { + name: 'John Smith', + email: 'john.smith@company.com', + signingOrder: 1 + } + ], + fields: [ + { + recipientEmail: 'john.smith@company.com', + type: 'signature', + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: 'john.smith@company.com', + type: 'date', + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + } + ], + documentName: 'Employment Contract - John Smith', + documentDescription: 'Please review and sign your employment contract' + }, + { + recipients: [ + { + name: 'Jane Doe', + email: 'jane.doe@company.com', + signingOrder: 1 + } + ], + fields: [ + { + recipientEmail: 'jane.doe@company.com', + type: 'signature', + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: 'jane.doe@company.com', + type: 'date', + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + } + ], + documentName: 'Employment Contract - Jane Doe', + documentDescription: 'Please review and sign your employment contract' + } +]; + +// Create form data +const formData = new FormData(); +formData.append('sourceType', 'file'); +formData.append('file', fs.createReadStream('./contract_template.pdf')); +formData.append('batchName', 'Q4 Employment Contracts'); +formData.append('documentName', 'Employment Contract'); +formData.append('documentDescription', 'Please review and sign your employment contract'); +formData.append('senderName', 'HR Department'); +formData.append('senderEmail', 'hr@company.com'); +formData.append('documents', JSON.stringify(documents)); + +// Send request to bulk ingest endpoint +const response = await fetch(`${BASE_URL}/turbosign/bulk/ingest`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + ...formData.getHeaders() + }, + body: formData +}); + +const result = await response.json(); + +if (result.success) { + console.log('✅ Bulk batch created successfully'); + console.log('Batch ID:', result.batchId); + console.log('Batch Name:', result.batchName); + console.log('Total Jobs:', result.totalJobs); + console.log('Status:', result.status); + console.log('\n📧 Documents will be sent to recipients asynchronously'); + console.log('💡 Tip: Use the batch ID to monitor progress and list jobs'); +} else { + console.error('❌ Error:', result.error || result.message); + if (result.code) { + console.error('Error Code:', result.code); + } + if (result.data?.invalidDocuments) { + console.error('Invalid Documents:', JSON.stringify(result.data.invalidDocuments, null, 2)); + } +} diff --git a/static/scripts/turbosign/api/bulk/ingest/nodejs/fastify.js b/static/scripts/turbosign/api/bulk/ingest/nodejs/fastify.js new file mode 100644 index 0000000..b2c063f --- /dev/null +++ b/static/scripts/turbosign/api/bulk/ingest/nodejs/fastify.js @@ -0,0 +1,136 @@ +const FormData = require('form-data'); +const fs = require('fs'); +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Fastify route handler example +async function bulkIngest(request, reply) { + try { + // Prepare documents array - each document represents one signing job + const documents = [ + { + recipients: [ + { + name: 'John Smith', + email: 'john.smith@company.com', + signingOrder: 1 + } + ], + fields: [ + { + recipientEmail: 'john.smith@company.com', + type: 'signature', + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: 'john.smith@company.com', + type: 'date', + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + } + ], + documentName: 'Employment Contract - John Smith', + documentDescription: 'Please review and sign your employment contract' + }, + { + recipients: [ + { + name: 'Jane Doe', + email: 'jane.doe@company.com', + signingOrder: 1 + } + ], + fields: [ + { + recipientEmail: 'jane.doe@company.com', + type: 'signature', + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: 'jane.doe@company.com', + type: 'date', + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + } + ], + documentName: 'Employment Contract - Jane Doe', + documentDescription: 'Please review and sign your employment contract' + } + ]; + + // Create form data + const formData = new FormData(); + formData.append('sourceType', 'file'); + formData.append('file', fs.createReadStream('./contract_template.pdf')); + formData.append('batchName', 'Q4 Employment Contracts'); + formData.append('documentName', 'Employment Contract'); + formData.append('documentDescription', 'Please review and sign your employment contract'); + formData.append('senderName', 'HR Department'); + formData.append('senderEmail', 'hr@company.com'); + formData.append('documents', JSON.stringify(documents)); + + // Send request to bulk ingest endpoint + const response = await fetch(`${BASE_URL}/turbosign/bulk/ingest`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + ...formData.getHeaders() + }, + body: formData + }); + + const result = await response.json(); + + if (result.success) { + return reply.send({ + success: true, + batchId: result.batchId, + batchName: result.batchName, + totalJobs: result.totalJobs, + status: result.status, + message: 'Bulk batch created successfully' + }); + } else { + return reply.code(400).send({ + success: false, + error: result.error || 'Failed to create bulk batch', + code: result.code, + data: result.data + }); + } + } catch (error) { + console.error('Error creating bulk batch:', error); + return reply.code(500).send({ + success: false, + error: 'Internal server error', + message: error.message + }); + } +} + +// Export for use in Fastify app +module.exports = { bulkIngest }; diff --git a/static/scripts/turbosign/api/bulk/ingest/python/fastapi.py b/static/scripts/turbosign/api/bulk/ingest/python/fastapi.py new file mode 100644 index 0000000..18e14f2 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/ingest/python/fastapi.py @@ -0,0 +1,156 @@ +import json +import requests +from fastapi import FastAPI, HTTPException, UploadFile, File +from pydantic import BaseModel + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = FastAPI() + +class BulkIngestResponse(BaseModel): + success: bool + batchId: str + batchName: str + totalJobs: int + status: str + message: str + +@app.post('/bulk-ingest', response_model=BulkIngestResponse) +async def bulk_ingest(file: UploadFile = File(...)): + try: + # Prepare documents array - each document represents one signing job + documents = [ + { + 'recipients': [ + { + 'name': 'John Smith', + 'email': 'john.smith@company.com', + 'signingOrder': 1 + } + ], + 'fields': [ + { + 'recipientEmail': 'john.smith@company.com', + 'type': 'signature', + 'page': 1, + 'x': 100, + 'y': 200, + 'width': 200, + 'height': 80, + 'required': True + }, + { + 'recipientEmail': 'john.smith@company.com', + 'type': 'date', + 'page': 1, + 'x': 100, + 'y': 300, + 'width': 150, + 'height': 30, + 'required': True + } + ], + 'documentName': 'Employment Contract - John Smith', + 'documentDescription': 'Please review and sign your employment contract' + }, + { + 'recipients': [ + { + 'name': 'Jane Doe', + 'email': 'jane.doe@company.com', + 'signingOrder': 1 + } + ], + 'fields': [ + { + 'recipientEmail': 'jane.doe@company.com', + 'type': 'signature', + 'page': 1, + 'x': 100, + 'y': 200, + 'width': 200, + 'height': 80, + 'required': True + }, + { + 'recipientEmail': 'jane.doe@company.com', + 'type': 'date', + 'page': 1, + 'x': 100, + 'y': 300, + 'width': 150, + 'height': 30, + 'required': True + } + ], + 'documentName': 'Employment Contract - Jane Doe', + 'documentDescription': 'Please review and sign your employment contract' + } + ] + + # Prepare file + files = { + 'file': (file.filename, file.file, file.content_type) + } + + # Prepare form data + data = { + 'sourceType': 'file', + 'batchName': 'Q4 Employment Contracts', + 'documentName': 'Employment Contract', + 'documentDescription': 'Please review and sign your employment contract', + 'senderName': 'HR Department', + 'senderEmail': 'hr@company.com', + 'documents': json.dumps(documents) + } + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send request + response = requests.post( + f'{BASE_URL}/turbosign/bulk/ingest', + headers=headers, + data=data, + files=files + ) + + result = response.json() + + if result.get('success'): + return BulkIngestResponse( + success=True, + batchId=result['batchId'], + batchName=result['batchName'], + totalJobs=result['totalJobs'], + status=result['status'], + message=result['message'] + ) + else: + raise HTTPException( + status_code=400, + detail={ + 'error': result.get('error', 'Failed to create bulk batch'), + 'code': result.get('code'), + 'data': result.get('data') + } + ) + + except HTTPException: + raise + except Exception as error: + print(f'Error creating bulk batch: {error}') + raise HTTPException( + status_code=500, + detail={ + 'error': 'Internal server error', + 'message': str(error) + } + ) diff --git a/static/scripts/turbosign/api/bulk/ingest/python/flask.py b/static/scripts/turbosign/api/bulk/ingest/python/flask.py new file mode 100644 index 0000000..15c506f --- /dev/null +++ b/static/scripts/turbosign/api/bulk/ingest/python/flask.py @@ -0,0 +1,144 @@ +import json +import requests +from flask import Flask, jsonify + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = Flask(__name__) + +@app.route('/bulk-ingest', methods=['POST']) +def bulk_ingest(): + try: + # Prepare documents array - each document represents one signing job + documents = [ + { + 'recipients': [ + { + 'name': 'John Smith', + 'email': 'john.smith@company.com', + 'signingOrder': 1 + } + ], + 'fields': [ + { + 'recipientEmail': 'john.smith@company.com', + 'type': 'signature', + 'page': 1, + 'x': 100, + 'y': 200, + 'width': 200, + 'height': 80, + 'required': True + }, + { + 'recipientEmail': 'john.smith@company.com', + 'type': 'date', + 'page': 1, + 'x': 100, + 'y': 300, + 'width': 150, + 'height': 30, + 'required': True + } + ], + 'documentName': 'Employment Contract - John Smith', + 'documentDescription': 'Please review and sign your employment contract' + }, + { + 'recipients': [ + { + 'name': 'Jane Doe', + 'email': 'jane.doe@company.com', + 'signingOrder': 1 + } + ], + 'fields': [ + { + 'recipientEmail': 'jane.doe@company.com', + 'type': 'signature', + 'page': 1, + 'x': 100, + 'y': 200, + 'width': 200, + 'height': 80, + 'required': True + }, + { + 'recipientEmail': 'jane.doe@company.com', + 'type': 'date', + 'page': 1, + 'x': 100, + 'y': 300, + 'width': 150, + 'height': 30, + 'required': True + } + ], + 'documentName': 'Employment Contract - Jane Doe', + 'documentDescription': 'Please review and sign your employment contract' + } + ] + + # Prepare file + files = { + 'file': ('contract_template.pdf', open('./contract_template.pdf', 'rb'), 'application/pdf') + } + + # Prepare form data + data = { + 'sourceType': 'file', + 'batchName': 'Q4 Employment Contracts', + 'documentName': 'Employment Contract', + 'documentDescription': 'Please review and sign your employment contract', + 'senderName': 'HR Department', + 'senderEmail': 'hr@company.com', + 'documents': json.dumps(documents) + } + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send request + response = requests.post( + f'{BASE_URL}/turbosign/bulk/ingest', + headers=headers, + data=data, + files=files + ) + + result = response.json() + + if result.get('success'): + return jsonify({ + 'success': True, + 'batchId': result['batchId'], + 'batchName': result['batchName'], + 'totalJobs': result['totalJobs'], + 'status': result['status'], + 'message': result['message'] + }), 200 + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'Failed to create bulk batch'), + 'code': result.get('code'), + 'data': result.get('data') + }), 400 + + except Exception as error: + print(f'Error creating bulk batch: {error}') + return jsonify({ + 'success': False, + 'error': 'Internal server error', + 'message': str(error) + }), 500 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/scripts/turbosign/api/bulk/list-batches/nodejs/express.js b/static/scripts/turbosign/api/bulk/list-batches/nodejs/express.js new file mode 100644 index 0000000..eb0c760 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-batches/nodejs/express.js @@ -0,0 +1,53 @@ +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Prepare query parameters +const queryParams = new URLSearchParams({ + limit: '20', + offset: '0', + status: 'pending,processing,completed' // Can be single value or comma-separated + // startDate: '2024-01-01', // Optional: Filter by start date + // endDate: '2024-12-31', // Optional: Filter by end date + // query: 'Q4' // Optional: Search batches by name +}); + +// Send GET request to list batches endpoint +const response = await fetch(`${BASE_URL}/turbosign/bulk/batches?${queryParams}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } +}); + +const result = await response.json(); + +if (result.data) { + console.log('✅ Successfully retrieved batches'); + console.log('Total Records:', result.data.totalRecords); + console.log('Batches in this page:', result.data.batches.length); + console.log('\n📦 Batches:'); + + result.data.batches.forEach((batch, index) => { + console.log(`\n${index + 1}. ${batch.batchName}`); + console.log(' Batch ID:', batch.id); + console.log(' Status:', batch.status); + console.log(' Total Jobs:', batch.totalJobs); + console.log(' Succeeded:', batch.succeededJobs); + console.log(' Failed:', batch.failedJobs); + console.log(' Pending:', batch.pendingJobs); + console.log(' Created:', new Date(batch.createdOn).toLocaleString()); + }); + + console.log('\n💡 Tip: Use limit and offset parameters for pagination'); +} else { + console.error('❌ Error:', result.error || 'Failed to retrieve batches'); + if (result.code) { + console.error('Error Code:', result.code); + } +} diff --git a/static/scripts/turbosign/api/bulk/list-batches/nodejs/fastify.js b/static/scripts/turbosign/api/bulk/list-batches/nodejs/fastify.js new file mode 100644 index 0000000..518afb1 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-batches/nodejs/fastify.js @@ -0,0 +1,57 @@ +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Fastify route handler example +async function listBatches(request, reply) { + try { + // Prepare query parameters + const queryParams = new URLSearchParams({ + limit: '20', + offset: '0', + status: 'pending,processing,completed' // Can be single value or comma-separated + // startDate: '2024-01-01', // Optional: Filter by start date + // endDate: '2024-12-31', // Optional: Filter by end date + // query: 'Q4' // Optional: Search batches by name + }); + + // Send GET request to list batches endpoint + const response = await fetch(`${BASE_URL}/turbosign/bulk/batches?${queryParams}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + }); + + const result = await response.json(); + + if (result.data) { + return reply.send({ + success: true, + totalRecords: result.data.totalRecords, + batches: result.data.batches + }); + } else { + return reply.code(400).send({ + success: false, + error: result.error || 'Failed to retrieve batches', + code: result.code + }); + } + } catch (error) { + console.error('Error retrieving batches:', error); + return reply.code(500).send({ + success: false, + error: 'Internal server error', + message: error.message + }); + } +} + +// Export for use in Fastify app +module.exports = { listBatches }; diff --git a/static/scripts/turbosign/api/bulk/list-batches/python/fastapi.py b/static/scripts/turbosign/api/bulk/list-batches/python/fastapi.py new file mode 100644 index 0000000..5f068fd --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-batches/python/fastapi.py @@ -0,0 +1,88 @@ +import json +import requests +from fastapi import FastAPI, HTTPException, Query +from pydantic import BaseModel +from typing import List, Optional + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = FastAPI() + +class BatchInfo(BaseModel): + id: str + batchName: str + status: str + totalJobs: int + succeededJobs: int + failedJobs: int + pendingJobs: int + createdOn: str + updatedOn: str + +class ListBatchesResponse(BaseModel): + success: bool + totalRecords: int + batches: List[BatchInfo] + +@app.get('/list-batches', response_model=ListBatchesResponse) +async def list_batches( + limit: Optional[int] = Query(20, description="Number of batches to return"), + offset: Optional[int] = Query(0, description="Number of batches to skip"), + status: Optional[str] = Query(None, description="Filter by status") +): + try: + # Prepare query parameters + params = { + 'limit': str(limit), + 'offset': str(offset) + } + + if status: + params['status'] = status + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send GET request + response = requests.get( + f'{BASE_URL}/turbosign/bulk/batches', + headers=headers, + params=params + ) + + result = response.json() + + if result.get('data'): + batches_data = result['data'] + return ListBatchesResponse( + success=True, + totalRecords=batches_data['totalRecords'], + batches=[BatchInfo(**batch) for batch in batches_data['batches']] + ) + else: + raise HTTPException( + status_code=400, + detail={ + 'error': result.get('error', 'Failed to retrieve batches'), + 'code': result.get('code') + } + ) + + except HTTPException: + raise + except Exception as error: + print(f'Error retrieving batches: {error}') + raise HTTPException( + status_code=500, + detail={ + 'error': 'Internal server error', + 'message': str(error) + } + ) diff --git a/static/scripts/turbosign/api/bulk/list-batches/python/flask.py b/static/scripts/turbosign/api/bulk/list-batches/python/flask.py new file mode 100644 index 0000000..9e0ac23 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-batches/python/flask.py @@ -0,0 +1,79 @@ +import json +import requests +from flask import Flask, jsonify, request + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = Flask(__name__) + +@app.route('/list-batches', methods=['GET']) +def list_batches(): + try: + # Prepare query parameters + params = { + 'limit': '20', + 'offset': '0', + 'status': 'pending,processing,completed' # Can be single value or comma-separated + # 'startDate': '2024-01-01', # Optional: Filter by start date + # 'endDate': '2024-12-31', # Optional: Filter by end date + # 'query': 'Q4' # Optional: Search batches by name + } + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send GET request + response = requests.get( + f'{BASE_URL}/turbosign/bulk/batches', + headers=headers, + params=params + ) + + result = response.json() + + if result.get('data'): + batches_data = result['data'] + formatted_batches = [] + + for batch in batches_data['batches']: + formatted_batches.append({ + 'id': batch['id'], + 'batchName': batch['batchName'], + 'status': batch['status'], + 'totalJobs': batch['totalJobs'], + 'succeededJobs': batch['succeededJobs'], + 'failedJobs': batch['failedJobs'], + 'pendingJobs': batch['pendingJobs'], + 'createdOn': batch['createdOn'], + 'updatedOn': batch['updatedOn'] + }) + + return jsonify({ + 'success': True, + 'totalRecords': batches_data['totalRecords'], + 'batches': formatted_batches + }), 200 + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'Failed to retrieve batches'), + 'code': result.get('code') + }), 400 + + except Exception as error: + print(f'Error retrieving batches: {error}') + return jsonify({ + 'success': False, + 'error': 'Internal server error', + 'message': str(error) + }), 500 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/scripts/turbosign/api/bulk/list-jobs/nodejs/express.js b/static/scripts/turbosign/api/bulk/list-jobs/nodejs/express.js new file mode 100644 index 0000000..b205481 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-jobs/nodejs/express.js @@ -0,0 +1,64 @@ +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; +const BATCH_ID = "YOUR_BATCH_ID"; // Replace with actual batch ID + +// Prepare query parameters +const queryParams = new URLSearchParams({ + limit: '20', + offset: '0', + // status: 'pending,processing,succeeded,failed', // Optional: Filter by status + // query: 'john' // Optional: Search by recipient name/email +}); + +// Send GET request to list jobs for a batch +const response = await fetch(`${BASE_URL}/turbosign/bulk/batch/${BATCH_ID}/jobs?${queryParams}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } +}); + +const result = await response.json(); + +if (result.data) { + console.log('✅ Successfully retrieved jobs for batch'); + console.log('Batch ID:', result.data.batchId); + console.log('Batch Name:', result.data.batchName); + console.log('Batch Status:', result.data.batchStatus); + console.log('\n📊 Statistics:'); + console.log('Total Jobs:', result.data.totalJobs); + console.log('Total Records:', result.data.totalRecords); + console.log('Succeeded:', result.data.succeededJobs); + console.log('Failed:', result.data.failedJobs); + console.log('Pending:', result.data.pendingJobs); + + console.log('\n📋 Jobs in this page:', result.data.jobs.length); + + result.data.jobs.forEach((job, index) => { + console.log(`\n${index + 1}. Job ID: ${job.id}`); + console.log(' Document:', job.documentName); + console.log(' Document ID:', job.documentId || 'N/A'); + console.log(' Status:', job.status); + console.log(' Recipients:', job.recipientEmails.join(', ')); + console.log(' Attempts:', job.attempts); + if (job.errorMessage) { + console.log(' Error:', job.errorMessage); + console.log(' Error Code:', job.errorCode); + } + console.log(' Created:', new Date(job.createdOn).toLocaleString()); + console.log(' Updated:', new Date(job.updatedOn).toLocaleString()); + }); + + console.log('\n💡 Tip: Use status filter to find failed or pending jobs'); +} else { + console.error('❌ Error:', result.error || 'Failed to retrieve jobs'); + if (result.code) { + console.error('Error Code:', result.code); + } +} diff --git a/static/scripts/turbosign/api/bulk/list-jobs/nodejs/fastify.js b/static/scripts/turbosign/api/bulk/list-jobs/nodejs/fastify.js new file mode 100644 index 0000000..d294bd0 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-jobs/nodejs/fastify.js @@ -0,0 +1,67 @@ +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; +const BATCH_ID = "YOUR_BATCH_ID"; // Replace with actual batch ID + +// Fastify route handler example +async function listJobs(request, reply) { + try { + // Prepare query parameters + const queryParams = new URLSearchParams({ + limit: '20', + offset: '0' + // status: 'pending,processing,succeeded,failed', // Optional: Filter by status + // query: 'john' // Optional: Search by recipient name/email + }); + + // Send GET request to list jobs for a batch + const response = await fetch(`${BASE_URL}/turbosign/bulk/batch/${BATCH_ID}/jobs?${queryParams}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + }); + + const result = await response.json(); + + if (result.data) { + // Jobs include: id, batchId, documentId, documentName, status, recipientEmails, + // attempts, errorCode, errorMessage, createdOn, updatedOn, lastAttemptedAt + return reply.send({ + success: true, + batchId: result.data.batchId, + batchName: result.data.batchName, + batchStatus: result.data.batchStatus, + statistics: { + totalJobs: result.data.totalJobs, + totalRecords: result.data.totalRecords, + succeededJobs: result.data.succeededJobs, + failedJobs: result.data.failedJobs, + pendingJobs: result.data.pendingJobs + }, + jobs: result.data.jobs + }); + } else { + return reply.code(400).send({ + success: false, + error: result.error || 'Failed to retrieve jobs', + code: result.code + }); + } + } catch (error) { + console.error('Error retrieving jobs:', error); + return reply.code(500).send({ + success: false, + error: 'Internal server error', + message: error.message + }); + } +} + +// Export for use in Fastify app +module.exports = { listJobs }; diff --git a/static/scripts/turbosign/api/bulk/list-jobs/python/fastapi.py b/static/scripts/turbosign/api/bulk/list-jobs/python/fastapi.py new file mode 100644 index 0000000..84e0e7a --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-jobs/python/fastapi.py @@ -0,0 +1,111 @@ +import json +import requests +from fastapi import FastAPI, HTTPException, Path, Query +from pydantic import BaseModel +from typing import List, Optional, Any + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" +BATCH_ID = "YOUR_BATCH_ID" # Replace with actual batch ID + +app = FastAPI() + +class JobStatistics(BaseModel): + totalJobs: int + totalRecords: int + succeededJobs: int + failedJobs: int + pendingJobs: int + +class JobInfo(BaseModel): + id: str + batchId: str + documentId: Optional[str] + documentName: str + status: str + recipientEmails: List[str] + attempts: int + errorCode: Optional[str] = None + errorMessage: Optional[str] = None + createdOn: str + updatedOn: str + lastAttemptedAt: Optional[str] = None + +class ListJobsResponse(BaseModel): + success: bool + batchId: str + batchName: str + batchStatus: str + statistics: JobStatistics + jobs: List[JobInfo] + +@app.get('/list-jobs', response_model=ListJobsResponse) +async def list_jobs( + limit: Optional[int] = Query(20, description="Number of jobs to return"), + offset: Optional[int] = Query(0, description="Number of jobs to skip"), + status: Optional[str] = Query(None, description="Filter by status") +): + try: + # Prepare query parameters + params = { + 'limit': str(limit), + 'offset': str(offset) + } + + if status: + params['status'] = status + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send GET request + response = requests.get( + f'{BASE_URL}/turbosign/bulk/batch/{BATCH_ID}/jobs', + headers=headers, + params=params + ) + + result = response.json() + + if result.get('data'): + jobs_data = result['data'] + return ListJobsResponse( + success=True, + batchId=jobs_data['batchId'], + batchName=jobs_data['batchName'], + batchStatus=jobs_data['batchStatus'], + statistics=JobStatistics( + totalJobs=jobs_data['totalJobs'], + totalRecords=jobs_data['totalRecords'], + succeededJobs=jobs_data['succeededJobs'], + failedJobs=jobs_data['failedJobs'], + pendingJobs=jobs_data['pendingJobs'] + ), + jobs=jobs_data['jobs'] + ) + else: + raise HTTPException( + status_code=400, + detail={ + 'error': result.get('error', 'Failed to retrieve jobs'), + 'code': result.get('code') + } + ) + + except HTTPException: + raise + except Exception as error: + print(f'Error retrieving jobs: {error}') + raise HTTPException( + status_code=500, + detail={ + 'error': 'Internal server error', + 'message': str(error) + } + ) diff --git a/static/scripts/turbosign/api/bulk/list-jobs/python/flask.py b/static/scripts/turbosign/api/bulk/list-jobs/python/flask.py new file mode 100644 index 0000000..a581c17 --- /dev/null +++ b/static/scripts/turbosign/api/bulk/list-jobs/python/flask.py @@ -0,0 +1,93 @@ +import json +import requests +from flask import Flask, jsonify, request + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" +BATCH_ID = "YOUR_BATCH_ID" # Replace with actual batch ID + +app = Flask(__name__) + +@app.route('/list-jobs', methods=['GET']) +def list_jobs(): + try: + # Prepare query parameters + params = { + 'limit': '20', + 'offset': '0' + # 'status': 'pending,processing,succeeded,failed', # Optional: Filter by status + # 'query': 'john' # Optional: Search by recipient name/email + } + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send GET request + response = requests.get( + f'{BASE_URL}/turbosign/bulk/batch/{BATCH_ID}/jobs', + headers=headers, + params=params + ) + + result = response.json() + + if result.get('data'): + jobs_data = result['data'] + formatted_jobs = [] + + for job in jobs_data['jobs']: + job_info = { + 'id': job['id'], + 'batchId': job['batchId'], + 'documentId': job.get('documentId'), + 'documentName': job['documentName'], + 'status': job['status'], + 'recipientEmails': job['recipientEmails'], + 'attempts': job['attempts'], + 'createdOn': job['createdOn'], + 'updatedOn': job['updatedOn'], + 'lastAttemptedAt': job.get('lastAttemptedAt') + } + if job.get('errorMessage'): + job_info['errorMessage'] = job['errorMessage'] + job_info['errorCode'] = job['errorCode'] + + formatted_jobs.append(job_info) + + return jsonify({ + 'success': True, + 'batchId': jobs_data['batchId'], + 'batchName': jobs_data['batchName'], + 'batchStatus': jobs_data['batchStatus'], + 'statistics': { + 'totalJobs': jobs_data['totalJobs'], + 'totalRecords': jobs_data['totalRecords'], + 'succeededJobs': jobs_data['succeededJobs'], + 'failedJobs': jobs_data['failedJobs'], + 'pendingJobs': jobs_data['pendingJobs'] + }, + 'jobs': formatted_jobs + }), 200 + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'Failed to retrieve jobs'), + 'code': result.get('code') + }), 400 + + except Exception as error: + print(f'Error retrieving jobs: {error}') + return jsonify({ + 'success': False, + 'error': 'Internal server error', + 'message': str(error) + }), 500 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/csharp/controller.cs b/static/scripts/turbosign/api/single-step/prepare-for-review/csharp/controller.cs new file mode 100644 index 0000000..3d5f49d --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/csharp/controller.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace TurboSign.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class SignatureController : ControllerBase + { + private const string API_TOKEN = "YOUR_API_TOKEN"; + private const string ORG_ID = "YOUR_ORGANIZATION_ID"; + private const string BASE_URL = "https://api.turbodocx.com"; + private readonly HttpClient _httpClient; + + public SignatureController(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient(); + } + + [HttpPost("prepare-for-review")] + public async Task PrepareForReview(IFormFile file) + { + try + { + using var formData = new MultipartFormDataContent(); + + // Add file + var fileContent = new StreamContent(file.OpenReadStream()); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); + formData.Add(fileContent, "file", file.FileName); + + // Add document metadata + formData.Add(new StringContent("Contract Agreement"), "documentName"); + formData.Add(new StringContent("Please review and sign this contract"), "documentDescription"); + formData.Add(new StringContent("Your Company"), "senderName"); + formData.Add(new StringContent("sender@company.com"), "senderEmail"); + + // Add recipients (as JSON string) + var recipients = JsonSerializer.Serialize(new[] + { + new + { + name = "John Smith", + email = "john.smith@company.com", + signingOrder = 1}, + new + { + name = "Jane Doe", + email = "jane.doe@partner.com", + signingOrder = 2} + }); + formData.Add(new StringContent(recipients), "recipients"); + + // Add fields (as JSON string) - Coordinate-based + var fields = JsonSerializer.Serialize(new[] + { + new + { + recipientEmail = "john.smith@company.com", + type = "signature", + page = 1, + x = 100, + y = 200, + width = 200, + height = 80, + required = true + }, + new + { + recipientEmail = "john.smith@company.com", + type = "date", + page = 1, + x = 100, + y = 300, + width = 150, + height = 30, + required = true + }, + new + { + recipientEmail = "jane.doe@partner.com", + type = "signature", + page = 1, + x = 350, + y = 200, + width = 200, + height = 80, + required = true + } + }); + formData.Add(new StringContent(fields), "fields"); + + // Set headers + var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/turbosign/single/prepare-for-review") + { + Content = formData + }; + request.Headers.Add("Authorization", $"Bearer {API_TOKEN}"); + request.Headers.Add("x-rapiddocx-org-id", ORG_ID); + request.Headers.Add("User-Agent", "TurboDocx API Client"); + + // Send request + var response = await _httpClient.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(responseContent); + + if (result.GetProperty("success").GetBoolean()) + { + return Ok(new + { + success = true, + documentId = result.GetProperty("documentId").GetString(), + status = result.GetProperty("status").GetString(), + previewUrl = result.GetProperty("previewUrl").GetString(), + recipients = result.GetProperty("recipients"), + message = "Document prepared for review successfully" + }); + } + else + { + return BadRequest(new + { + success = false, + error = result.GetProperty("error").GetString(), + code = result.TryGetProperty("code", out var code) ? code.GetString() : null + }); + } + } + catch (Exception ex) + { + return StatusCode(500, new + { + success = false, + error = "Internal server error", + message = ex.Message + }); + } + } + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/csharp/minimal.cs b/static/scripts/turbosign/api/single-step/prepare-for-review/csharp/minimal.cs new file mode 100644 index 0000000..c22b3fc --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/csharp/minimal.cs @@ -0,0 +1,109 @@ +using System.Net.Http.Headers; +using System.Text.Json; + +// Configuration - Update these values +const string API_TOKEN = "YOUR_API_TOKEN"; +const string ORG_ID = "YOUR_ORGANIZATION_ID"; +const string BASE_URL = "https://api.turbodocx.com"; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddHttpClient(); + +var app = builder.Build(); + +app.MapPost("/prepare-for-review", async (IFormFile file, IHttpClientFactory httpClientFactory) => +{ + try + { + var httpClient = httpClientFactory.CreateClient(); + using var formData = new MultipartFormDataContent(); + + // Add file + var fileContent = new StreamContent(file.OpenReadStream()); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); + formData.Add(fileContent, "file", file.FileName); + + // Add document metadata + formData.Add(new StringContent("Contract Agreement"), "documentName"); + formData.Add(new StringContent("Please review and sign this contract"), "documentDescription"); + + // Add recipients (as JSON string) + var recipients = JsonSerializer.Serialize(new[] + { + new + { + name = "John Smith", + email = "john.smith@company.com", + signingOrder = 1} + }); + formData.Add(new StringContent(recipients), "recipients"); + + // Add fields (as JSON string) - Coordinate-based + var fields = JsonSerializer.Serialize(new[] + { + new + { + recipientEmail = "john.smith@company.com", + type = "signature", + page = 1, + x = 100, + y = 200, + width = 200, + height = 80, + required = true + }, + new + { + recipientEmail = "john.smith@company.com", + type = "date", + page = 1, + x = 350, + y = 200, + width = 150, + height = 30, + required = true + } + }); + formData.Add(new StringContent(fields), "fields"); + + // Set headers + var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/turbosign/single/prepare-for-review") + { + Content = formData + }; + request.Headers.Add("Authorization", $"Bearer {API_TOKEN}"); + request.Headers.Add("x-rapiddocx-org-id", ORG_ID); + request.Headers.Add("User-Agent", "TurboDocx API Client"); + + // Send request + var response = await httpClient.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(responseContent); + + if (result.GetProperty("success").GetBoolean()) + { + return Results.Ok(new + { + success = true, + documentId = result.GetProperty("documentId").GetString(), + status = result.GetProperty("status").GetString(), + previewUrl = result.GetProperty("previewUrl").GetString(), + message = "Document prepared for review successfully" + }); + } + else + { + return Results.BadRequest(new + { + success = false, + error = result.GetProperty("error").GetString() + }); + } + } + catch (Exception ex) + { + return Results.Problem(ex.Message); + } +}); + +app.Run(); diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/go.go b/static/scripts/turbosign/api/single-step/prepare-for-review/go.go new file mode 100644 index 0000000..d385c30 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/go.go @@ -0,0 +1,188 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" +) + +// Configuration - Update these values +const ( + API_TOKEN = "YOUR_API_TOKEN" + ORG_ID = "YOUR_ORGANIZATION_ID" + BASE_URL = "https://api.turbodocx.com" +) + +type Recipient struct { + Name string `json:"name"` + Email string `json:"email"` + SigningOrder int `json:"signingOrder"` +} + +type Field struct { + RecipientEmail string `json:"recipientEmail"` + Type string `json:"type"` + Page int `json:"page,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Required bool `json:"required"` +} + +type Response struct { + Success bool `json:"success"` + DocumentID string `json:"documentId"` + Status string `json:"status"` + PreviewURL string `json:"previewUrl"` + Recipients []map[string]interface{} `json:"recipients"` + Message string `json:"message"` + Error string `json:"error,omitempty"` + Code string `json:"code,omitempty"` +} + +func prepareDocumentForReview() error { + // Prepare recipients + recipients := []Recipient{ + { + Name: "John Smith", + Email: "john.smith@company.com", + SigningOrder: 1, + }, + { + Name: "Jane Doe", + Email: "jane.doe@partner.com", + SigningOrder: 2, + }, + } + + recipientsJSON, err := json.Marshal(recipients) + if err != nil { + return fmt.Errorf("failed to marshal recipients: %w", err) + } + + // Prepare fields - Coordinate-based + fields := []Field{ + { + RecipientEmail: "john.smith@company.com", + Type: "signature", + Page: 1, + X: 100, + Y: 200, + Width: 200, + Height: 80, + Required: true, + }, + { + RecipientEmail: "john.smith@company.com", + Type: "date", + Page: 1, + X: 100, + Y: 300, + Width: 150, + Height: 30, + Required: true, + }, + { + RecipientEmail: "jane.doe@partner.com", + Type: "signature", + Page: 1, + X: 350, + Y: 200, + Width: 200, + Height: 80, + Required: true, + }, + } + + fieldsJSON, err := json.Marshal(fields) + if err != nil { + return fmt.Errorf("failed to marshal fields: %w", err) + } + + // Create multipart form + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // Add file + file, err := os.Open("./contract.pdf") + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + part, err := writer.CreateFormFile("file", "contract.pdf") + if err != nil { + return fmt.Errorf("failed to create form file: %w", err) + } + if _, err := io.Copy(part, file); err != nil { + return fmt.Errorf("failed to copy file: %w", err) + } + + // Add form fields + writer.WriteField("documentName", "Contract Agreement") + writer.WriteField("documentDescription", "Please review and sign this contract") + writer.WriteField("senderName", "Your Company") + writer.WriteField("senderEmail", "sender@company.com") + writer.WriteField("recipients", string(recipientsJSON)) + writer.WriteField("fields", string(fieldsJSON)) + + err = writer.Close() + if err != nil { + return fmt.Errorf("failed to close writer: %w", err) + } + + // Create request + req, err := http.NewRequest("POST", BASE_URL+"/turbosign/single/prepare-for-review", body) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + // Set headers + req.Header.Set("Authorization", "Bearer "+API_TOKEN) + req.Header.Set("x-rapiddocx-org-id", ORG_ID) + req.Header.Set("User-Agent", "TurboDocx API Client") + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // Send request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + // Parse response + var result Response + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + + if result.Success { + fmt.Println("✅ Document prepared for review") + fmt.Printf("Document ID: %s\n", result.DocumentID) + fmt.Printf("Status: %s\n", result.Status) + fmt.Printf("Preview URL: %s\n", result.PreviewURL) + fmt.Println("\n🔍 Open this URL to preview the document:") + fmt.Println(result.PreviewURL) + } else { + fmt.Printf("❌ Error: %s\n", result.Error) + if result.Code != "" { + fmt.Printf("Error Code: %s\n", result.Code) + } + return fmt.Errorf("API error: %s", result.Error) + } + + return nil +} + +func main() { + if err := prepareDocumentForReview(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/java.java b/static/scripts/turbosign/api/single-step/prepare-for-review/java.java new file mode 100644 index 0000000..8550ebe --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/java.java @@ -0,0 +1,128 @@ +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; + +public class TurboSignPrepareForReview { + + // Configuration - Update these values + private static final String API_TOKEN = "YOUR_API_TOKEN"; + private static final String ORG_ID = "YOUR_ORGANIZATION_ID"; + private static final String BASE_URL = "https://api.turbodocx.com"; + + private final OkHttpClient client = new OkHttpClient(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public void prepareDocumentForReview(File pdfFile) throws IOException { + // Prepare recipients + List> recipients = new ArrayList<>(); + Map recipient1 = new HashMap<>(); + recipient1.put("name", "John Smith"); + recipient1.put("email", "john.smith@company.com"); + recipient1.put("signingOrder", 1); + Map recipient2 = new HashMap<>(); + recipient2.put("name", "Jane Doe"); + recipient2.put("email", "jane.doe@partner.com"); + recipient2.put("signingOrder", 2); + String recipientsJson = objectMapper.writeValueAsString(recipients); + + // Prepare fields - Coordinate-based + List> fields = new ArrayList<>(); + + Map field1 = new HashMap<>(); + field1.put("recipientEmail", "john.smith@company.com"); + field1.put("type", "signature"); + field1.put("page", 1); + field1.put("x", 100); + field1.put("y", 200); + field1.put("width", 200); + field1.put("height", 80); + field1.put("required", true); + fields.add(field1); + + Map field2 = new HashMap<>(); + field2.put("recipientEmail", "john.smith@company.com"); + field2.put("type", "date"); + field2.put("page", 1); + field2.put("x", 100); + field2.put("y", 300); + field2.put("width", 150); + field2.put("height", 30); + field2.put("required", true); + fields.add(field2); + + Map field3 = new HashMap<>(); + field3.put("recipientEmail", "jane.doe@partner.com"); + field3.put("type", "signature"); + field3.put("page", 1); + field3.put("x", 350); + field3.put("y", 200); + field3.put("width", 200); + field3.put("height", 80); + field3.put("required", true); + fields.add(field3); + + String fieldsJson = objectMapper.writeValueAsString(fields); + + // Build multipart request + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", pdfFile.getName(), + RequestBody.create(pdfFile, MediaType.parse("application/pdf"))) + .addFormDataPart("documentName", "Contract Agreement") + .addFormDataPart("documentDescription", "Please review and sign this contract") + .addFormDataPart("senderName", "Your Company") + .addFormDataPart("senderEmail", "sender@company.com") + .addFormDataPart("recipients", recipientsJson) + .addFormDataPart("fields", fieldsJson) + .build(); + + // Build request + Request request = new Request.Builder() + .url(BASE_URL + "/turbosign/single/prepare-for-review") + .addHeader("Authorization", "Bearer " + API_TOKEN) + .addHeader("x-rapiddocx-org-id", ORG_ID) + .addHeader("User-Agent", "TurboDocx API Client") + .post(requestBody) + .build(); + + // Execute request + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected response: " + response); + } + + String responseBody = response.body().string(); + Map result = objectMapper.readValue(responseBody, Map.class); + + if ((Boolean) result.get("success")) { + System.out.println("✅ Document prepared for review"); + System.out.println("Document ID: " + result.get("documentId")); + System.out.println("Status: " + result.get("status")); + System.out.println("Preview URL: " + result.get("previewUrl")); + System.out.println("\n🔍 Open this URL to preview the document:"); + System.out.println(result.get("previewUrl")); + } else { + System.err.println("❌ Error: " + result.get("error")); + if (result.containsKey("code")) { + System.err.println("Error Code: " + result.get("code")); + } + } + } + } + + public static void main(String[] args) { + try { + TurboSignPrepareForReview service = new TurboSignPrepareForReview(); + File pdfFile = new File("./contract.pdf"); + service.prepareDocumentForReview(pdfFile); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/nodejs/express.js b/static/scripts/turbosign/api/single-step/prepare-for-review/nodejs/express.js new file mode 100644 index 0000000..26f254e --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/nodejs/express.js @@ -0,0 +1,102 @@ +const FormData = require('form-data'); +const fs = require('fs'); +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Prepare form data +const formData = new FormData(); + +// Add file +formData.append('file', fs.createReadStream('./contract.pdf')); + +// Add document metadata +formData.append('documentName', 'Contract Agreement'); +formData.append('documentDescription', 'Please review and sign this contract'); +formData.append('senderName', 'Your Company'); +formData.append('senderEmail', 'sender@company.com'); + +// Add recipients (as JSON string) +const recipients = JSON.stringify([ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1 + }, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2 + } +]); +formData.append('recipients', recipients); + +// Add fields (as JSON string) - Coordinate-based positioning +const fields = JSON.stringify([ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + }, + { + recipientEmail: "jane.doe@partner.com", + type: "signature", + page: 1, + x: 350, + y: 200, + width: 200, + height: 80, + required: true + } +]); +formData.append('fields', fields); + +// Send request to prepare-for-review endpoint +const response = await fetch(`${BASE_URL}/turbosign/single/prepare-for-review`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + ...formData.getHeaders() + }, + body: formData +}); + +const result = await response.json(); + +if (result.success) { + console.log('✅ Document prepared for review'); + console.log('Document ID:', result.documentId); + console.log('Status:', result.status); + console.log('Preview URL:', result.previewUrl); + console.log('\n📋 Recipients:'); + result.recipients.forEach(r => { + console.log(` - ${r.name} (${r.email}) - ID: ${r.id}`); + }); + console.log('\n🔍 Open this URL to preview the document:'); + console.log(result.previewUrl); +} else { + console.error('❌ Error:', result.error || result.message); + if (result.code) { + console.error('Error Code:', result.code); + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/nodejs/fastify.js b/static/scripts/turbosign/api/single-step/prepare-for-review/nodejs/fastify.js new file mode 100644 index 0000000..232109d --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/nodejs/fastify.js @@ -0,0 +1,113 @@ +const FormData = require('form-data'); +const fs = require('fs'); +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Fastify route handler example +async function prepareDocumentForReview(request, reply) { + try { + // Prepare form data + const formData = new FormData(); + + // Add file + formData.append('file', fs.createReadStream('./contract.pdf')); + + // Add document metadata + formData.append('documentName', 'Contract Agreement'); + formData.append('documentDescription', 'Please review and sign this contract'); + + // Add recipients (as JSON string) + const recipients = JSON.stringify([ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1 + }, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2 + } + ]); + formData.append('recipients', recipients); + + // Add fields (as JSON string) - Coordinate-based positioning + const fields = JSON.stringify([ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + }, + { + recipientEmail: "jane.doe@partner.com", + type: "signature", + page: 1, + x: 350, + y: 200, + width: 200, + height: 80, + required: true + } + ]); + formData.append('fields', fields); + + // Send request + const response = await fetch(`${BASE_URL}/turbosign/single/prepare-for-review`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + ...formData.getHeaders() + }, + body: formData + }); + + const result = await response.json(); + + if (result.success) { + return reply.send({ + success: true, + documentId: result.documentId, + status: result.status, + previewUrl: result.previewUrl, + message: 'Document prepared for review successfully' + }); + } else { + return reply.code(400).send({ + success: false, + error: result.error || 'Failed to prepare document', + code: result.code + }); + } + } catch (error) { + console.error('Error preparing document:', error); + return reply.code(500).send({ + success: false, + error: 'Internal server error', + message: error.message + }); + } +} + +// Export for use in Fastify app +module.exports = { prepareDocumentForReview }; diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/php.php b/static/scripts/turbosign/api/single-step/prepare-for-review/php.php new file mode 100644 index 0000000..4bf6bb5 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/php.php @@ -0,0 +1,118 @@ + "John Smith", + "email" => "john.smith@company.com", + "signingOrder" => 1 + ], + [ + "name" => "Jane Doe", + "email" => "jane.doe@partner.com", + "signingOrder" => 2 + ] +]); + +// Prepare fields (as JSON string) - Coordinate-based positioning +$fields = json_encode([ + [ + "recipientEmail" => "john.smith@company.com", + "type" => "signature", + "page" => 1, + "x" => 100, + "y" => 200, + "width" => 200, + "height" => 80, + "required" => true + ], + [ + "recipientEmail" => "john.smith@company.com", + "type" => "date", + "page" => 1, + "x" => 100, + "y" => 300, + "width" => 150, + "height" => 30, + "required" => true + ], + [ + "recipientEmail" => "jane.doe@partner.com", + "type" => "signature", + "page" => 1, + "x" => 350, + "y" => 200, + "width" => 200, + "height" => 80, + "required" => true + ] +]); + +// Prepare POST fields +$postFields = [ + 'file' => new CURLFile('./contract.pdf', 'application/pdf', 'contract.pdf'), + 'documentName' => 'Contract Agreement', + 'documentDescription' => 'Please review and sign this contract', + 'senderName' => 'Your Company', + 'senderEmail' => 'sender@company.com', + 'recipients' => $recipients, + 'fields' => $fields +]; + +// Set cURL options +curl_setopt_array($ch, [ + CURLOPT_URL => $BASE_URL . '/turbosign/single/prepare-for-review', + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $postFields, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $API_TOKEN, + 'x-rapiddocx-org-id: ' . $ORG_ID, + 'User-Agent: TurboDocx API Client' + ] +]); + +// Execute request +$response = curl_exec($ch); +$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + +// Check for errors +if (curl_errno($ch)) { + echo 'Error: ' . curl_error($ch) . "\n"; + curl_close($ch); + exit(1); +} + +curl_close($ch); + +// Parse response +$result = json_decode($response, true); + +if ($result['success']) { + echo "✅ Document prepared for review\n"; + echo "Document ID: " . $result['documentId'] . "\n"; + echo "Status: " . $result['status'] . "\n"; + echo "Preview URL: " . $result['previewUrl'] . "\n"; + echo "\n📋 Recipients:\n"; + foreach ($result['recipients'] as $recipient) { + echo " - {$recipient['name']} ({$recipient['email']}) - ID: {$recipient['id']}\n"; + } + echo "\n🔍 Open this URL to preview the document:\n"; + echo $result['previewUrl'] . "\n"; +} else { + echo "❌ Error: " . ($result['error'] ?? $result['message']) . "\n"; + if (isset($result['code'])) { + echo "Error Code: " . $result['code'] . "\n"; + } + exit(1); +} + +?> diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/powershell.ps1 b/static/scripts/turbosign/api/single-step/prepare-for-review/powershell.ps1 new file mode 100644 index 0000000..d1c637b --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/powershell.ps1 @@ -0,0 +1,138 @@ +# Configuration - Update these values +$API_TOKEN = "YOUR_API_TOKEN" +$ORG_ID = "YOUR_ORGANIZATION_ID" +$BASE_URL = "https://api.turbodocx.com" + +# Prepare recipients (as JSON string) +$recipients = @( + @{ + name = "John Smith" + email = "john.smith@company.com" + signingOrder = 1 + }, + @{ + name = "Jane Doe" + email = "jane.doe@partner.com" + signingOrder = 2 + } +) | ConvertTo-Json -Compress + +# Prepare fields (as JSON string) - Coordinate-based +$fields = @( + @{ + recipientEmail = "john.smith@company.com" + type = "signature" + page = 1 + x = 100 + y = 200 + width = 200 + height = 80 + required = $true + }, + @{ + recipientEmail = "john.smith@company.com" + type = "date" + page = 1 + x = 100 + y = 300 + width = 150 + height = 30 + required = $true + }, + @{ + recipientEmail = "jane.doe@partner.com" + type = "signature" + page = 1 + x = 350 + y = 200 + width = 200 + height = 80 + required = $true + } +) | ConvertTo-Json -Compress -Depth 10 + +# Prepare file +$filePath = "./contract.pdf" +$fileName = [System.IO.Path]::GetFileName($filePath) +$fileBytes = [System.IO.File]::ReadAllBytes($filePath) +$fileContent = [System.Text.Encoding]::GetEncoding('iso-8859-1').GetString($fileBytes) + +# Create boundary +$boundary = [System.Guid]::NewGuid().ToString() + +# Build multipart form data +$bodyLines = @( + "--$boundary", + "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"", + "Content-Type: application/pdf", + "", + $fileContent, + "--$boundary", + "Content-Disposition: form-data; name=`"documentName`"", + "", + "Contract Agreement", + "--$boundary", + "Content-Disposition: form-data; name=`"documentDescription`"", + "", + "Please review and sign this contract", + "--$boundary", + "Content-Disposition: form-data; name=`"senderName`"", + "", + "Your Company", + "--$boundary", + "Content-Disposition: form-data; name=`"senderEmail`"", + "", + "sender@company.com", + "--$boundary", + "Content-Disposition: form-data; name=`"recipients`"", + "", + $recipients, + "--$boundary", + "Content-Disposition: form-data; name=`"fields`"", + "", + $fields, + "--$boundary--" +) + +$body = $bodyLines -join "`r`n" + +# Prepare headers +$headers = @{ + "Authorization" = "Bearer $API_TOKEN" + "x-rapiddocx-org-id" = $ORG_ID + "User-Agent" = "TurboDocx API Client" + "Content-Type" = "multipart/form-data; boundary=$boundary" +} + +try { + # Send request + $response = Invoke-RestMethod ` + -Uri "$BASE_URL/turbosign/single/prepare-for-review" ` + -Method Post ` + -Headers $headers ` + -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) + + if ($response.success) { + Write-Host "✅ Document prepared for review" -ForegroundColor Green + Write-Host "Document ID: $($response.documentId)" + Write-Host "Status: $($response.status)" + Write-Host "Preview URL: $($response.previewUrl)" + Write-Host "`n📋 Recipients:" + foreach ($recipient in $response.recipients) { + Write-Host " - $($recipient.name) ($($recipient.email)) - ID: $($recipient.id)" + } + Write-Host "`n🔍 Open this URL to preview the document:" + Write-Host $response.previewUrl + } else { + Write-Host "❌ Error: $($response.error)" -ForegroundColor Red + if ($response.code) { + Write-Host "Error Code: $($response.code)" -ForegroundColor Red + } + exit 1 + } +} +catch { + Write-Host "❌ Exception: $_" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + exit 1 +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/python/fastapi.py b/static/scripts/turbosign/api/single-step/prepare-for-review/python/fastapi.py new file mode 100644 index 0000000..82b2717 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/python/fastapi.py @@ -0,0 +1,130 @@ +import json +import requests +from fastapi import FastAPI, HTTPException, UploadFile, File +from pydantic import BaseModel + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = FastAPI() + +class PrepareResponse(BaseModel): + success: bool + documentId: str + status: str + previewUrl: str + message: str + +@app.post('/prepare-for-review', response_model=PrepareResponse) +async def prepare_document_for_review(file: UploadFile = File(...)): + try: + # Prepare form data + files = { + 'file': (file.filename, file.file, file.content_type) + } + + # Prepare form fields + data = { + 'documentName': 'Contract Agreement', + 'documentDescription': 'Please review and sign this contract', + 'senderName': 'Your Company', + 'senderEmail': 'sender@company.com' + } + + # Add recipients (as JSON string) + recipients = json.dumps([ + { + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1 + }, + { + "name": "Jane Doe", + "email": "jane.doe@partner.com", + "signingOrder": 2 + } + ]) + data['recipients'] = recipients + + # Add fields (as JSON string) - Coordinate-based positioning + fields = json.dumps([ + { + "recipientEmail": "john.smith@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "required": True + }, + { + "recipientEmail": "john.smith@company.com", + "type": "date", + "page": 1, + "x": 100, + "y": 300, + "width": 150, + "height": 30, + "required": True + }, + { + "recipientEmail": "jane.doe@partner.com", + "type": "signature", + "page": 1, + "x": 350, + "y": 200, + "width": 200, + "height": 80, + "required": True + } + ]) + data['fields'] = fields + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send request + response = requests.post( + f'{BASE_URL}/turbosign/single/prepare-for-review', + headers=headers, + data=data, + files=files + ) + + result = response.json() + + if result.get('success'): + return PrepareResponse( + success=True, + documentId=result['documentId'], + status=result['status'], + previewUrl=result['previewUrl'], + message='Document prepared for review successfully' + ) + else: + raise HTTPException( + status_code=400, + detail={ + 'error': result.get('error', 'Failed to prepare document'), + 'code': result.get('code') + } + ) + + except HTTPException: + raise + except Exception as error: + print(f'Error preparing document: {error}') + raise HTTPException( + status_code=500, + detail={ + 'error': 'Internal server error', + 'message': str(error) + } + ) diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/python/flask.py b/static/scripts/turbosign/api/single-step/prepare-for-review/python/flask.py new file mode 100644 index 0000000..a0534ee --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/python/flask.py @@ -0,0 +1,120 @@ +import json +import requests +from flask import Flask, jsonify + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = Flask(__name__) + +@app.route('/prepare-for-review', methods=['POST']) +def prepare_document_for_review(): + try: + # Prepare form data + files = { + 'file': ('contract.pdf', open('./contract.pdf', 'rb'), 'application/pdf') + } + + # Prepare form fields + data = { + 'documentName': 'Contract Agreement', + 'documentDescription': 'Please review and sign this contract', + 'senderName': 'Your Company', + 'senderEmail': 'sender@company.com' + } + + # Add recipients (as JSON string) + recipients = json.dumps([ + { + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1 + }, + { + "name": "Jane Doe", + "email": "jane.doe@partner.com", + "signingOrder": 2 + } + ]) + data['recipients'] = recipients + + # Add fields (as JSON string) - Coordinate-based positioning + fields = json.dumps([ + { + "recipientEmail": "john.smith@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "required": True + }, + { + "recipientEmail": "john.smith@company.com", + "type": "date", + "page": 1, + "x": 100, + "y": 300, + "width": 150, + "height": 30, + "required": True + }, + { + "recipientEmail": "jane.doe@partner.com", + "type": "signature", + "page": 1, + "x": 350, + "y": 200, + "width": 200, + "height": 80, + "required": True + } + ]) + data['fields'] = fields + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send request + response = requests.post( + f'{BASE_URL}/turbosign/single/prepare-for-review', + headers=headers, + data=data, + files=files + ) + + result = response.json() + + if result.get('success'): + return jsonify({ + 'success': True, + 'documentId': result['documentId'], + 'status': result['status'], + 'previewUrl': result['previewUrl'], + 'recipients': result['recipients'], + 'message': 'Document prepared for review successfully' + }), 200 + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'Failed to prepare document'), + 'code': result.get('code') + }), 400 + + except Exception as error: + print(f'Error preparing document: {error}') + return jsonify({ + 'success': False, + 'error': 'Internal server error', + 'message': str(error) + }), 500 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/scripts/turbosign/api/single-step/prepare-for-review/ruby.rb b/static/scripts/turbosign/api/single-step/prepare-for-review/ruby.rb new file mode 100644 index 0000000..5fc0eb7 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-review/ruby.rb @@ -0,0 +1,131 @@ +require 'net/http' +require 'uri' +require 'json' +require 'mime/types' + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +def prepare_document_for_review + # Prepare recipients + recipients = [ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1}, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2} + ] + + # Prepare fields - Coordinate-based + fields = [ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + }, + { + recipientEmail: "jane.doe@partner.com", + type: "signature", + page: 1, + x: 350, + y: 200, + width: 200, + height: 80, + required: true + } + ] + + # Create multipart form data + uri = URI.parse("#{BASE_URL}/turbosign/single/prepare-for-review") + + boundary = "----WebKitFormBoundary#{rand(1000000000)}" + + post_body = [] + + # Add file + file_path = "./contract.pdf" + file_content = File.read(file_path) + post_body << "--#{boundary}\r\n" + post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"contract.pdf\"\r\n" + post_body << "Content-Type: application/pdf\r\n\r\n" + post_body << file_content + post_body << "\r\n" + + # Add form fields + form_fields = { + 'documentName' => 'Contract Agreement', + 'documentDescription' => 'Please review and sign this contract', + 'senderName' => 'Your Company', + 'senderEmail' => 'sender@company.com', + 'recipients' => recipients.to_json, + 'fields' => fields.to_json + } + + form_fields.each do |key, value| + post_body << "--#{boundary}\r\n" + post_body << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" + post_body << value.to_s + post_body << "\r\n" + end + + post_body << "--#{boundary}--\r\n" + + # Create HTTP request + request = Net::HTTP::Post.new(uri.request_uri) + request["Authorization"] = "Bearer #{API_TOKEN}" + request["x-rapiddocx-org-id"] = ORG_ID + request["User-Agent"] = "TurboDocx API Client" + request["Content-Type"] = "multipart/form-data; boundary=#{boundary}" + request.body = post_body.join + + # Send request + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + + response = http.request(request) + result = JSON.parse(response.body) + + if result['success'] + puts "✅ Document prepared for review" + puts "Document ID: #{result['documentId']}" + puts "Status: #{result['status']}" + puts "Preview URL: #{result['previewUrl']}" + puts "\n📋 Recipients:" + result['recipients'].each do |recipient| + puts " - #{recipient['name']} (#{recipient['email']}) - ID: #{recipient['id']}" + end + puts "\n🔍 Open this URL to preview the document:" + puts result['previewUrl'] + else + puts "❌ Error: #{result['error'] || result['message']}" + puts "Error Code: #{result['code']}" if result['code'] + exit 1 + end +rescue => e + puts "❌ Exception: #{e.message}" + puts e.backtrace + exit 1 +end + +# Run the function +prepare_document_for_review diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/csharp/controller.cs b/static/scripts/turbosign/api/single-step/prepare-for-signing/csharp/controller.cs new file mode 100644 index 0000000..6d468d6 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/csharp/controller.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace TurboSign.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class SignatureController : ControllerBase + { + private const string API_TOKEN = "YOUR_API_TOKEN"; + private const string ORG_ID = "YOUR_ORGANIZATION_ID"; + private const string BASE_URL = "https://api.turbodocx.com"; + private readonly HttpClient _httpClient; + + public SignatureController(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient(); + } + + [HttpPost("prepare-for-signing")] + public async Task PrepareForSigning(IFormFile file) + { + try + { + using var formData = new MultipartFormDataContent(); + + // Add file + var fileContent = new StreamContent(file.OpenReadStream()); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); + formData.Add(fileContent, "file", file.FileName); + + // Add document metadata + formData.Add(new StringContent("Contract Agreement"), "documentName"); + formData.Add(new StringContent("Please review and sign this contract"), "documentDescription"); + formData.Add(new StringContent("Your Company"), "senderName"); + formData.Add(new StringContent("sender@company.com"), "senderEmail"); + + // Add recipients (as JSON string) + var recipients = JsonSerializer.Serialize(new[] + { + new + { + name = "John Smith", + email = "john.smith@company.com", + signingOrder = 1 + }, + new + { + name = "Jane Doe", + email = "jane.doe@partner.com", + signingOrder = 2 + } + }); + formData.Add(new StringContent(recipients), "recipients"); + + // Add fields (as JSON string) - Coordinate-based positioning + var fields = JsonSerializer.Serialize(new[] + { + new + { + recipientEmail = "john.smith@company.com", + type = "signature", + page = 1, + x = 100, + y = 200, + width = 200, + height = 80, + required = true + }, + new + { + recipientEmail = "john.smith@company.com", + type = "date", + page = 1, + x = 100, + y = 300, + width = 150, + height = 30, + required = true + }, + new + { + recipientEmail = "jane.doe@partner.com", + type = "signature", + page = 1, + x = 350, + y = 200, + width = 200, + height = 80, + required = true + } + }); + formData.Add(new StringContent(fields), "fields"); + + // Optional: Add CC emails + var ccEmails = JsonSerializer.Serialize(new[] { "manager@company.com", "legal@company.com" }); + formData.Add(new StringContent(ccEmails), "ccEmails"); + + // Set headers + var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/turbosign/single/prepare-for-signing") + { + Content = formData + }; + request.Headers.Add("Authorization", $"Bearer {API_TOKEN}"); + request.Headers.Add("x-rapiddocx-org-id", ORG_ID); + request.Headers.Add("User-Agent", "TurboDocx API Client"); + + // Send request + var response = await _httpClient.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(responseContent); + + if (result.GetProperty("success").GetBoolean()) + { + return Ok(new + { + success = true, + documentId = result.GetProperty("documentId").GetString(), + message = result.GetProperty("message").GetString() + }); + } + else + { + return BadRequest(new + { + success = false, + error = result.GetProperty("error").GetString(), + code = result.TryGetProperty("code", out var code) ? code.GetString() : null + }); + } + } + catch (Exception ex) + { + return StatusCode(500, new + { + success = false, + error = "Internal server error", + message = ex.Message + }); + } + } + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/csharp/minimal.cs b/static/scripts/turbosign/api/single-step/prepare-for-signing/csharp/minimal.cs new file mode 100644 index 0000000..084e203 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/csharp/minimal.cs @@ -0,0 +1,107 @@ +using System.Net.Http.Headers; +using System.Text.Json; + +// Configuration - Update these values +const string API_TOKEN = "YOUR_API_TOKEN"; +const string ORG_ID = "YOUR_ORGANIZATION_ID"; +const string BASE_URL = "https://api.turbodocx.com"; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddHttpClient(); + +var app = builder.Build(); + +app.MapPost("/prepare-for-signing", async (IFormFile file, IHttpClientFactory httpClientFactory) => +{ + try + { + var httpClient = httpClientFactory.CreateClient(); + using var formData = new MultipartFormDataContent(); + + // Add file + var fileContent = new StreamContent(file.OpenReadStream()); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); + formData.Add(fileContent, "file", file.FileName); + + // Add document metadata + formData.Add(new StringContent("Contract Agreement"), "documentName"); + formData.Add(new StringContent("Please review and sign this contract"), "documentDescription"); + + // Add recipients (as JSON string) + var recipients = JsonSerializer.Serialize(new[] + { + new + { + name = "John Smith", + email = "john.smith@company.com", + signingOrder = 1} + }); + formData.Add(new StringContent(recipients), "recipients"); + + // Add fields (as JSON string) - Coordinate-based + var fields = JsonSerializer.Serialize(new[] + { + new + { + recipientEmail = "john.smith@company.com", + type = "signature", + page = 1, + x = 100, + y = 200, + width = 200, + height = 80, + required = true + }, + new + { + recipientEmail = "john.smith@company.com", + type = "date", + page = 1, + x = 350, + y = 200, + width = 150, + height = 30, + required = true + } + }); + formData.Add(new StringContent(fields), "fields"); + + // Set headers + var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/turbosign/single/prepare-for-signing") + { + Content = formData + }; + request.Headers.Add("Authorization", $"Bearer {API_TOKEN}"); + request.Headers.Add("x-rapiddocx-org-id", ORG_ID); + request.Headers.Add("User-Agent", "TurboDocx API Client"); + + // Send request + var response = await httpClient.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(responseContent); + + if (result.GetProperty("success").GetBoolean()) + { + return Results.Ok(new + { + success = true, + documentId = result.GetProperty("documentId").GetString(), + message = result.GetProperty("message").GetString() + }); + } + else + { + return Results.BadRequest(new + { + success = false, + error = result.GetProperty("error").GetString() + }); + } + } + catch (Exception ex) + { + return Results.Problem(ex.Message); + } +}); + +app.Run(); diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/go.go b/static/scripts/turbosign/api/single-step/prepare-for-signing/go.go new file mode 100644 index 0000000..1caeaa5 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/go.go @@ -0,0 +1,192 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" +) + +// Configuration - Update these values +const ( + API_TOKEN = "YOUR_API_TOKEN" + ORG_ID = "YOUR_ORGANIZATION_ID" + BASE_URL = "https://api.turbodocx.com" +) + +type Recipient struct { + Name string `json:"name"` + Email string `json:"email"` + SigningOrder int `json:"signingOrder"` +} + +type Field struct { + RecipientEmail string `json:"recipientEmail"` + Type string `json:"type"` + Page int `json:"page,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Required bool `json:"required"` +} + +type Response struct { + Success bool `json:"success"` + DocumentID string `json:"documentId"` + Message string `json:"message"` + Error string `json:"error,omitempty"` + Code string `json:"code,omitempty"` +} + +func prepareDocumentForSigning() error { + // Prepare recipients + recipients := []Recipient{ + { + Name: "John Smith", + Email: "john.smith@company.com", + SigningOrder: 1, + }, + { + Name: "Jane Doe", + Email: "jane.doe@partner.com", + SigningOrder: 2, + }, + } + + recipientsJSON, err := json.Marshal(recipients) + if err != nil { + return fmt.Errorf("failed to marshal recipients: %w", err) + } + + // Prepare fields - Coordinate-based + fields := []Field{ + { + RecipientEmail: "john.smith@company.com", + Type: "signature", + Page: 1, + X: 100, + Y: 200, + Width: 200, + Height: 80, + Required: true, + }, + { + RecipientEmail: "john.smith@company.com", + Type: "date", + Page: 1, + X: 100, + Y: 300, + Width: 150, + Height: 30, + Required: true, + }, + { + RecipientEmail: "jane.doe@partner.com", + Type: "signature", + Page: 1, + X: 350, + Y: 200, + Width: 200, + Height: 80, + Required: true, + }, + } + + fieldsJSON, err := json.Marshal(fields) + if err != nil { + return fmt.Errorf("failed to marshal fields: %w", err) + } + + // Optional: CC emails + ccEmails := []string{"manager@company.com", "legal@company.com"} + ccEmailsJSON, err := json.Marshal(ccEmails) + if err != nil { + return fmt.Errorf("failed to marshal ccEmails: %w", err) + } + + // Create multipart form + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // Add file + file, err := os.Open("./contract.pdf") + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + part, err := writer.CreateFormFile("file", "contract.pdf") + if err != nil { + return fmt.Errorf("failed to create form file: %w", err) + } + if _, err := io.Copy(part, file); err != nil { + return fmt.Errorf("failed to copy file: %w", err) + } + + // Add form fields + writer.WriteField("documentName", "Contract Agreement") + writer.WriteField("documentDescription", "Please review and sign this contract") + writer.WriteField("senderName", "Your Company") + writer.WriteField("senderEmail", "sender@company.com") + writer.WriteField("recipients", string(recipientsJSON)) + writer.WriteField("fields", string(fieldsJSON)) + writer.WriteField("ccEmails", string(ccEmailsJSON)) + + err = writer.Close() + if err != nil { + return fmt.Errorf("failed to close writer: %w", err) + } + + // Create request + req, err := http.NewRequest("POST", BASE_URL+"/turbosign/single/prepare-for-signing", body) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + // Set headers + req.Header.Set("Authorization", "Bearer "+API_TOKEN) + req.Header.Set("x-rapiddocx-org-id", ORG_ID) + req.Header.Set("User-Agent", "TurboDocx API Client") + req.Header.Set("Content-Type", writer.FormDataContentType()) + + // Send request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + // Parse response + var result Response + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + + if result.Success { + fmt.Println("✅ Document sent for signing") + fmt.Printf("Document ID: %s\n", result.DocumentID) + fmt.Printf("Message: %s\n", result.Message) + fmt.Println("\n📧 Emails are being sent to recipients asynchronously") + fmt.Println("💡 Tip: Set up webhooks to receive notifications when signing is complete") + } else { + fmt.Printf("❌ Error: %s\n", result.Error) + if result.Code != "" { + fmt.Printf("Error Code: %s\n", result.Code) + } + return fmt.Errorf("API error: %s", result.Error) + } + + return nil +} + +func main() { + if err := prepareDocumentForSigning(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/java.java b/static/scripts/turbosign/api/single-step/prepare-for-signing/java.java new file mode 100644 index 0000000..ed82934 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/java.java @@ -0,0 +1,134 @@ +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; + +public class TurboSignPrepareForSigning { + + // Configuration - Update these values + private static final String API_TOKEN = "YOUR_API_TOKEN"; + private static final String ORG_ID = "YOUR_ORGANIZATION_ID"; + private static final String BASE_URL = "https://api.turbodocx.com"; + + private final OkHttpClient client = new OkHttpClient(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public void prepareDocumentForSigning(File pdfFile) throws IOException { + // Prepare recipients + List> recipients = new ArrayList<>(); + Map recipient1 = new HashMap<>(); + recipient1.put("name", "John Smith"); + recipient1.put("email", "john.smith@company.com"); + recipient1.put("signingOrder", 1); + Map recipient2 = new HashMap<>(); + recipient2.put("name", "Jane Doe"); + recipient2.put("email", "jane.doe@partner.com"); + recipient2.put("signingOrder", 2); + String recipientsJson = objectMapper.writeValueAsString(recipients); + + // Prepare fields - Coordinate-based + List> fields = new ArrayList<>(); + + Map field1 = new HashMap<>(); + field1.put("recipientEmail", "john.smith@company.com"); + field1.put("type", "signature"); + field1.put("page", 1); + field1.put("x", 100); + field1.put("y", 200); + field1.put("width", 200); + field1.put("height", 80); + field1.put("required", true); + fields.add(field1); + + Map field2 = new HashMap<>(); + field2.put("recipientEmail", "john.smith@company.com"); + field2.put("type", "date"); + field2.put("page", 1); + field2.put("x", 100); + field2.put("y", 300); + field2.put("width", 150); + field2.put("height", 30); + field2.put("required", true); + fields.add(field2); + + Map field3 = new HashMap<>(); + field3.put("recipientEmail", "jane.doe@partner.com"); + field3.put("type", "signature"); + field3.put("page", 1); + field3.put("x", 350); + field3.put("y", 200); + field3.put("width", 200); + field3.put("height", 80); + field3.put("required", true); + fields.add(field3); + + String fieldsJson = objectMapper.writeValueAsString(fields); + + // Optional: Add CC emails + List ccEmails = new ArrayList<>(); + ccEmails.add("manager@company.com"); + ccEmails.add("legal@company.com"); + String ccEmailsJson = objectMapper.writeValueAsString(ccEmails); + + // Build multipart request + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", pdfFile.getName(), + RequestBody.create(pdfFile, MediaType.parse("application/pdf"))) + .addFormDataPart("documentName", "Contract Agreement") + .addFormDataPart("documentDescription", "Please review and sign this contract") + .addFormDataPart("senderName", "Your Company") + .addFormDataPart("senderEmail", "sender@company.com") + .addFormDataPart("recipients", recipientsJson) + .addFormDataPart("fields", fieldsJson) + .addFormDataPart("ccEmails", ccEmailsJson) + .build(); + + // Build request + Request request = new Request.Builder() + .url(BASE_URL + "/turbosign/single/prepare-for-signing") + .addHeader("Authorization", "Bearer " + API_TOKEN) + .addHeader("x-rapiddocx-org-id", ORG_ID) + .addHeader("User-Agent", "TurboDocx API Client") + .post(requestBody) + .build(); + + // Execute request + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected response: " + response); + } + + String responseBody = response.body().string(); + Map result = objectMapper.readValue(responseBody, Map.class); + + if ((Boolean) result.get("success")) { + System.out.println("✅ Document sent for signing"); + System.out.println("Document ID: " + result.get("documentId")); + System.out.println("Message: " + result.get("message")); + System.out.println("\n📧 Emails are being sent to recipients asynchronously"); + System.out.println("💡 Tip: Set up webhooks to receive notifications when signing is complete"); + } else { + System.err.println("❌ Error: " + result.get("error")); + if (result.containsKey("code")) { + System.err.println("Error Code: " + result.get("code")); + } + } + } + } + + public static void main(String[] args) { + try { + TurboSignPrepareForSigning service = new TurboSignPrepareForSigning(); + File pdfFile = new File("./contract.pdf"); + service.prepareDocumentForSigning(pdfFile); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/nodejs/express.js b/static/scripts/turbosign/api/single-step/prepare-for-signing/nodejs/express.js new file mode 100644 index 0000000..fa1bbfd --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/nodejs/express.js @@ -0,0 +1,101 @@ +const FormData = require('form-data'); +const fs = require('fs'); +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Prepare form data +const formData = new FormData(); + +// Add file +formData.append('file', fs.createReadStream('./contract.pdf')); + +// Add document metadata +formData.append('documentName', 'Contract Agreement'); +formData.append('documentDescription', 'Please review and sign this contract'); +formData.append('senderName', 'Your Company'); +formData.append('senderEmail', 'sender@company.com'); + +// Add recipients (as JSON string) +const recipients = JSON.stringify([ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1 + }, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2 + } +]); +formData.append('recipients', recipients); + +// Add fields (as JSON string) - Coordinate-based positioning +const fields = JSON.stringify([ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + }, + { + recipientEmail: "jane.doe@partner.com", + type: "signature", + page: 1, + x: 350, + y: 200, + width: 200, + height: 80, + required: true + } +]); +formData.append('fields', fields); + +// Optional: Add CC emails +const ccEmails = JSON.stringify(["manager@company.com", "legal@company.com"]); +formData.append('ccEmails', ccEmails); + +// Send request to prepare-for-signing endpoint +const response = await fetch(`${BASE_URL}/turbosign/single/prepare-for-signing`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + ...formData.getHeaders() + }, + body: formData +}); + +const result = await response.json(); + +if (result.success) { + console.log('✅ Document sent for signing'); + console.log('Document ID:', result.documentId); + console.log('Message:', result.message); + console.log('\n📧 Emails are being sent to recipients asynchronously'); + console.log('💡 Tip: Set up webhooks to receive notifications when signing is complete'); +} else { + console.error('❌ Error:', result.error || result.message); + if (result.code) { + console.error('Error Code:', result.code); + } +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/nodejs/fastify.js b/static/scripts/turbosign/api/single-step/prepare-for-signing/nodejs/fastify.js new file mode 100644 index 0000000..93ae813 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/nodejs/fastify.js @@ -0,0 +1,111 @@ +const FormData = require('form-data'); +const fs = require('fs'); +const fetch = require('node-fetch'); + +// Configuration - Update these values +const API_TOKEN = "YOUR_API_TOKEN"; +const ORG_ID = "YOUR_ORGANIZATION_ID"; +const BASE_URL = "https://api.turbodocx.com"; + +// Fastify route handler example +async function prepareDocumentForSigning(request, reply) { + try { + // Prepare form data + const formData = new FormData(); + + // Add file + formData.append('file', fs.createReadStream('./contract.pdf')); + + // Add document metadata + formData.append('documentName', 'Contract Agreement'); + formData.append('documentDescription', 'Please review and sign this contract'); + + // Add recipients (as JSON string) + const recipients = JSON.stringify([ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1 + }, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2 + } + ]); + formData.append('recipients', recipients); + + // Add fields (as JSON string) - Coordinate-based positioning + const fields = JSON.stringify([ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + }, + { + recipientEmail: "jane.doe@partner.com", + type: "signature", + page: 1, + x: 350, + y: 200, + width: 200, + height: 80, + required: true + } + ]); + formData.append('fields', fields); + + // Send request + const response = await fetch(`${BASE_URL}/turbosign/single/prepare-for-signing`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_TOKEN}`, + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client', + ...formData.getHeaders() + }, + body: formData + }); + + const result = await response.json(); + + if (result.success) { + return reply.send({ + success: true, + documentId: result.documentId, + message: 'Document sent for signing. Emails are being sent to recipients.' + }); + } else { + return reply.code(400).send({ + success: false, + error: result.error || 'Failed to prepare document', + code: result.code + }); + } + } catch (error) { + console.error('Error preparing document:', error); + return reply.code(500).send({ + success: false, + error: 'Internal server error', + message: error.message + }); + } +} + +// Export for use in Fastify app +module.exports = { prepareDocumentForSigning }; diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/php.php b/static/scripts/turbosign/api/single-step/prepare-for-signing/php.php new file mode 100644 index 0000000..4c47e36 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/php.php @@ -0,0 +1,117 @@ + "John Smith", + "email" => "john.smith@company.com", + "signingOrder" => 1 + ], + [ + "name" => "Jane Doe", + "email" => "jane.doe@partner.com", + "signingOrder" => 2 + ] +]); + +// Prepare fields (as JSON string) - Coordinate-based positioning +$fields = json_encode([ + [ + "recipientEmail" => "john.smith@company.com", + "type" => "signature", + "page" => 1, + "x" => 100, + "y" => 200, + "width" => 200, + "height" => 80, + "required" => true + ], + [ + "recipientEmail" => "john.smith@company.com", + "type" => "date", + "page" => 1, + "x" => 100, + "y" => 300, + "width" => 150, + "height" => 30, + "required" => true + ], + [ + "recipientEmail" => "jane.doe@partner.com", + "type" => "signature", + "page" => 1, + "x" => 350, + "y" => 200, + "width" => 200, + "height" => 80, + "required" => true + ] +]); + +// Optional: Add CC emails +$ccEmails = json_encode(["manager@company.com", "legal@company.com"]); + +// Prepare POST fields +$postFields = [ + 'file' => new CURLFile('./contract.pdf', 'application/pdf', 'contract.pdf'), + 'documentName' => 'Contract Agreement', + 'documentDescription' => 'Please review and sign this contract', + 'senderName' => 'Your Company', + 'senderEmail' => 'sender@company.com', + 'recipients' => $recipients, + 'fields' => $fields, + 'ccEmails' => $ccEmails +]; + +// Set cURL options +curl_setopt_array($ch, [ + CURLOPT_URL => $BASE_URL . '/turbosign/single/prepare-for-signing', + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $postFields, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $API_TOKEN, + 'x-rapiddocx-org-id: ' . $ORG_ID, + 'User-Agent: TurboDocx API Client' + ] +]); + +// Execute request +$response = curl_exec($ch); +$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + +// Check for errors +if (curl_errno($ch)) { + echo 'Error: ' . curl_error($ch) . "\n"; + curl_close($ch); + exit(1); +} + +curl_close($ch); + +// Parse response +$result = json_decode($response, true); + +if ($result['success']) { + echo "✅ Document sent for signing\n"; + echo "Document ID: " . $result['documentId'] . "\n"; + echo "Message: " . $result['message'] . "\n"; + echo "\n📧 Emails are being sent to recipients asynchronously\n"; + echo "💡 Tip: Set up webhooks to receive notifications when signing is complete\n"; +} else { + echo "❌ Error: " . ($result['error'] ?? $result['message']) . "\n"; + if (isset($result['code'])) { + echo "Error Code: " . $result['code'] . "\n"; + } + exit(1); +} + +?> diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/powershell.ps1 b/static/scripts/turbosign/api/single-step/prepare-for-signing/powershell.ps1 new file mode 100644 index 0000000..1f8d446 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/powershell.ps1 @@ -0,0 +1,140 @@ +# Configuration - Update these values +$API_TOKEN = "YOUR_API_TOKEN" +$ORG_ID = "YOUR_ORGANIZATION_ID" +$BASE_URL = "https://api.turbodocx.com" + +# Prepare recipients (as JSON string) +$recipients = @( + @{ + name = "John Smith" + email = "john.smith@company.com" + signingOrder = 1 + }, + @{ + name = "Jane Doe" + email = "jane.doe@partner.com" + signingOrder = 2 + } +) | ConvertTo-Json -Compress + +# Prepare fields (as JSON string) - Coordinate-based +$fields = @( + @{ + recipientEmail = "john.smith@company.com" + type = "signature" + page = 1 + x = 100 + y = 200 + width = 200 + height = 80 + required = $true + }, + @{ + recipientEmail = "john.smith@company.com" + type = "date" + page = 1 + x = 100 + y = 300 + width = 150 + height = 30 + required = $true + }, + @{ + recipientEmail = "jane.doe@partner.com" + type = "signature" + page = 1 + x = 350 + y = 200 + width = 200 + height = 80 + required = $true + } +) | ConvertTo-Json -Compress -Depth 10 + +# Optional: CC emails +$ccEmails = @("manager@company.com", "legal@company.com") | ConvertTo-Json -Compress + +# Prepare file +$filePath = "./contract.pdf" +$fileName = [System.IO.Path]::GetFileName($filePath) +$fileBytes = [System.IO.File]::ReadAllBytes($filePath) +$fileContent = [System.Text.Encoding]::GetEncoding('iso-8859-1').GetString($fileBytes) + +# Create boundary +$boundary = [System.Guid]::NewGuid().ToString() + +# Build multipart form data +$bodyLines = @( + "--$boundary", + "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"", + "Content-Type: application/pdf", + "", + $fileContent, + "--$boundary", + "Content-Disposition: form-data; name=`"documentName`"", + "", + "Contract Agreement", + "--$boundary", + "Content-Disposition: form-data; name=`"documentDescription`"", + "", + "Please review and sign this contract", + "--$boundary", + "Content-Disposition: form-data; name=`"senderName`"", + "", + "Your Company", + "--$boundary", + "Content-Disposition: form-data; name=`"senderEmail`"", + "", + "sender@company.com", + "--$boundary", + "Content-Disposition: form-data; name=`"recipients`"", + "", + $recipients, + "--$boundary", + "Content-Disposition: form-data; name=`"fields`"", + "", + $fields, + "--$boundary", + "Content-Disposition: form-data; name=`"ccEmails`"", + "", + $ccEmails, + "--$boundary--" +) + +$body = $bodyLines -join "`r`n" + +# Prepare headers +$headers = @{ + "Authorization" = "Bearer $API_TOKEN" + "x-rapiddocx-org-id" = $ORG_ID + "User-Agent" = "TurboDocx API Client" + "Content-Type" = "multipart/form-data; boundary=$boundary" +} + +try { + # Send request + $response = Invoke-RestMethod ` + -Uri "$BASE_URL/turbosign/single/prepare-for-signing" ` + -Method Post ` + -Headers $headers ` + -Body ([System.Text.Encoding]::UTF8.GetBytes($body)) + + if ($response.success) { + Write-Host "✅ Document sent for signing" -ForegroundColor Green + Write-Host "Document ID: $($response.documentId)" + Write-Host "Message: $($response.message)" + Write-Host "`n📧 Emails are being sent to recipients asynchronously" + Write-Host "💡 Tip: Set up webhooks to receive notifications when signing is complete" + } else { + Write-Host "❌ Error: $($response.error)" -ForegroundColor Red + if ($response.code) { + Write-Host "Error Code: $($response.code)" -ForegroundColor Red + } + exit 1 + } +} +catch { + Write-Host "❌ Exception: $_" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + exit 1 +} diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/python/fastapi.py b/static/scripts/turbosign/api/single-step/prepare-for-signing/python/fastapi.py new file mode 100644 index 0000000..bbf8919 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/python/fastapi.py @@ -0,0 +1,126 @@ +import json +import requests +from fastapi import FastAPI, HTTPException, UploadFile, File +from pydantic import BaseModel + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = FastAPI() + +class SigningResponse(BaseModel): + success: bool + documentId: str + message: str + +@app.post('/prepare-for-signing', response_model=SigningResponse) +async def prepare_document_for_signing(file: UploadFile = File(...)): + try: + # Prepare form data + files = { + 'file': (file.filename, file.file, file.content_type) + } + + # Prepare form fields + data = { + 'documentName': 'Contract Agreement', + 'documentDescription': 'Please review and sign this contract', + 'senderName': 'Your Company', + 'senderEmail': 'sender@company.com' + } + + # Add recipients (as JSON string) + recipients = json.dumps([ + { + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1 + }, + { + "name": "Jane Doe", + "email": "jane.doe@partner.com", + "signingOrder": 2 + } + ]) + data['recipients'] = recipients + + # Add fields (as JSON string) - Coordinate-based positioning + fields = json.dumps([ + { + "recipientEmail": "john.smith@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "required": True + }, + { + "recipientEmail": "john.smith@company.com", + "type": "date", + "page": 1, + "x": 100, + "y": 300, + "width": 150, + "height": 30, + "required": True + }, + { + "recipientEmail": "jane.doe@partner.com", + "type": "signature", + "page": 1, + "x": 350, + "y": 200, + "width": 200, + "height": 80, + "required": True + } + ]) + data['fields'] = fields + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send request + response = requests.post( + f'{BASE_URL}/turbosign/single/prepare-for-signing', + headers=headers, + data=data, + files=files + ) + + result = response.json() + + if result.get('success'): + return SigningResponse( + success=True, + documentId=result['documentId'], + message=result['message'] + ) + else: + raise HTTPException( + status_code=400, + detail={ + 'error': result.get('error', 'Failed to prepare document'), + 'code': result.get('code') + } + ) + + except HTTPException: + raise + except Exception as error: + print(f'Error preparing document: {error}') + raise HTTPException( + status_code=500, + detail={ + 'error': 'Internal server error', + 'message': str(error) + } + ) diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/python/flask.py b/static/scripts/turbosign/api/single-step/prepare-for-signing/python/flask.py new file mode 100644 index 0000000..6937c02 --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/python/flask.py @@ -0,0 +1,121 @@ +import json +import requests +from flask import Flask, jsonify + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +app = Flask(__name__) + +@app.route('/prepare-for-signing', methods=['POST']) +def prepare_document_for_signing(): + try: + # Prepare form data + files = { + 'file': ('contract.pdf', open('./contract.pdf', 'rb'), 'application/pdf') + } + + # Prepare form fields + data = { + 'documentName': 'Contract Agreement', + 'documentDescription': 'Please review and sign this contract', + 'senderName': 'Your Company', + 'senderEmail': 'sender@company.com' + } + + # Add recipients (as JSON string) + recipients = json.dumps([ + { + "name": "John Smith", + "email": "john.smith@company.com", + "signingOrder": 1 + }, + { + "name": "Jane Doe", + "email": "jane.doe@partner.com", + "signingOrder": 2 + } + ]) + data['recipients'] = recipients + + # Add fields (as JSON string) - Coordinate-based positioning + fields = json.dumps([ + { + "recipientEmail": "john.smith@company.com", + "type": "signature", + "page": 1, + "x": 100, + "y": 200, + "width": 200, + "height": 80, + "required": True + }, + { + "recipientEmail": "john.smith@company.com", + "type": "date", + "page": 1, + "x": 100, + "y": 300, + "width": 150, + "height": 30, + "required": True + }, + { + "recipientEmail": "jane.doe@partner.com", + "type": "signature", + "page": 1, + "x": 350, + "y": 200, + "width": 200, + "height": 80, + "required": True + } + ]) + data['fields'] = fields + + # Optional: Add CC emails + ccEmails = json.dumps(["manager@company.com", "legal@company.com"]) + data['ccEmails'] = ccEmails + + # Prepare headers + headers = { + 'Authorization': f'Bearer {API_TOKEN}', + 'x-rapiddocx-org-id': ORG_ID, + 'User-Agent': 'TurboDocx API Client' + } + + # Send request + response = requests.post( + f'{BASE_URL}/turbosign/single/prepare-for-signing', + headers=headers, + data=data, + files=files + ) + + result = response.json() + + if result.get('success'): + return jsonify({ + 'success': True, + 'documentId': result['documentId'], + 'message': result['message'] + }), 200 + else: + return jsonify({ + 'success': False, + 'error': result.get('error', 'Failed to prepare document'), + 'code': result.get('code') + }), 400 + + except Exception as error: + print(f'Error preparing document: {error}') + return jsonify({ + 'success': False, + 'error': 'Internal server error', + 'message': str(error) + }), 500 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/scripts/turbosign/api/single-step/prepare-for-signing/ruby.rb b/static/scripts/turbosign/api/single-step/prepare-for-signing/ruby.rb new file mode 100644 index 0000000..17825ac --- /dev/null +++ b/static/scripts/turbosign/api/single-step/prepare-for-signing/ruby.rb @@ -0,0 +1,130 @@ +require 'net/http' +require 'uri' +require 'json' +require 'mime/types' + +# Configuration - Update these values +API_TOKEN = "YOUR_API_TOKEN" +ORG_ID = "YOUR_ORGANIZATION_ID" +BASE_URL = "https://api.turbodocx.com" + +def prepare_document_for_signing + # Prepare recipients + recipients = [ + { + name: "John Smith", + email: "john.smith@company.com", + signingOrder: 1}, + { + name: "Jane Doe", + email: "jane.doe@partner.com", + signingOrder: 2} + ] + + # Prepare fields - Coordinate-based + fields = [ + { + recipientEmail: "john.smith@company.com", + type: "signature", + page: 1, + x: 100, + y: 200, + width: 200, + height: 80, + required: true + }, + { + recipientEmail: "john.smith@company.com", + type: "date", + page: 1, + x: 100, + y: 300, + width: 150, + height: 30, + required: true + }, + { + recipientEmail: "jane.doe@partner.com", + type: "signature", + page: 1, + x: 350, + y: 200, + width: 200, + height: 80, + required: true + } + ] + + # Optional: CC emails + cc_emails = ["manager@company.com", "legal@company.com"] + + # Create multipart form data + uri = URI.parse("#{BASE_URL}/turbosign/single/prepare-for-signing") + + boundary = "----WebKitFormBoundary#{rand(1000000000)}" + + post_body = [] + + # Add file + file_path = "./contract.pdf" + file_content = File.read(file_path) + post_body << "--#{boundary}\r\n" + post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"contract.pdf\"\r\n" + post_body << "Content-Type: application/pdf\r\n\r\n" + post_body << file_content + post_body << "\r\n" + + # Add form fields + form_fields = { + 'documentName' => 'Contract Agreement', + 'documentDescription' => 'Please review and sign this contract', + 'senderName' => 'Your Company', + 'senderEmail' => 'sender@company.com', + 'recipients' => recipients.to_json, + 'fields' => fields.to_json, + 'ccEmails' => cc_emails.to_json + } + + form_fields.each do |key, value| + post_body << "--#{boundary}\r\n" + post_body << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" + post_body << value.to_s + post_body << "\r\n" + end + + post_body << "--#{boundary}--\r\n" + + # Create HTTP request + request = Net::HTTP::Post.new(uri.request_uri) + request["Authorization"] = "Bearer #{API_TOKEN}" + request["x-rapiddocx-org-id"] = ORG_ID + request["User-Agent"] = "TurboDocx API Client" + request["Content-Type"] = "multipart/form-data; boundary=#{boundary}" + request.body = post_body.join + + # Send request + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + + response = http.request(request) + result = JSON.parse(response.body) + + if result['success'] + puts "✅ Document sent for signing" + puts "Document ID: #{result['documentId']}" + puts "Message: #{result['message']}" + puts "\n📧 Emails are being sent to recipients asynchronously" + puts "💡 Tip: Set up webhooks to receive notifications when signing is complete" + else + puts "❌ Error: #{result['error'] || result['message']}" + puts "Error Code: #{result['code']}" if result['code'] + exit 1 + end +rescue => e + puts "❌ Exception: #{e.message}" + puts e.backtrace + exit 1 +end + +# Run the function +prepare_document_for_signing