# TurboDocx Documentation > Turbocharging your Document Workflows This file contains all documentation content in a single document following the llmstxt.org standard. # Deliverable API This guide covers the TurboDocx Deliverable API — everything you need to programmatically generate documents from templates, manage deliverables, and download files using the v1 API endpoints. ![Deliverable API](/img/template-generation-api/template-api.jpg) ## Overview A **deliverable** is a document generated from a template by substituting variables with actual content. The Deliverable API lets you automate the entire document lifecycle: creation, retrieval, updates, downloads, and organization. ### Key Features - **Template-Based Generation**: Create documents by injecting variables into pre-configured templates - **Variable Substitution**: Support for text, rich text, images, and markdown content types - **Automatic PDF Generation**: PDFs are generated automatically when a deliverable is created - **File Downloads**: Download source files (DOCX/PPTX) or generated PDFs - **Tagging & Filtering**: Tag deliverables and filter by tags, search queries, and more - **Pagination & Sorting**: Built-in pagination and column sorting for list endpoints :::tip Prefer using an SDK? We offer official SDKs that handle authentication, error handling, and type safety for you. [View all SDKs →](/docs/SDKs) ::: ## TLDR; Quick Start Here's the fastest way to create a deliverable and download it: ```bash # 1. Create a deliverable from a template curl -X POST "https://api.turbodocx.com/v1/deliverable" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -d '{ "templateId": "YOUR_TEMPLATE_ID", "name": "My Document", "variables": [ { "placeholder": "{CompanyName}", "text": "Acme Corporation", "mimeType": "text" } ] }' # 2. Download the source file (DOCX/PPTX) curl -X GET "https://api.turbodocx.com/v1/deliverable/file/DELIVERABLE_ID" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ --output "my-document.docx" # 3. Download the PDF curl -X GET "https://api.turbodocx.com/v1/deliverable/file/pdf/DELIVERABLE_ID" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ --output "my-document.pdf" ``` ### All Endpoints at a Glance | Method | Endpoint | Description | | -------- | ----------------------------------------- | ---------------------------------- | | `GET` | `/v1/deliverable` | List deliverables | | `POST` | `/v1/deliverable` | Create deliverable from template | | `GET` | `/v1/deliverable/:id` | Get a single deliverable | | `PATCH` | `/v1/deliverable/:id` | Update a deliverable | | `DELETE` | `/v1/deliverable/:id` | Delete a deliverable (soft delete) | | `GET` | `/v1/deliverable/file/:deliverableId` | Download source file (DOCX/PPTX) | | `GET` | `/v1/deliverable/file/pdf/:deliverableId` | Download PDF file | ### Complete Workflow Example Here's a full workflow that creates a deliverable, retrieves it, and downloads both the source file and PDF: ## Prerequisites Before you begin, ensure you have: - **API Access Token**: Bearer token for authentication - **Organization ID**: Your organization identifier - **A Template**: At least one uploaded template with extracted variables ### 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 ## Authentication All Deliverable API requests require authentication using a Bearer token and your organization ID. ### Required Headers | Header | Value | Required | Description | | -------------------- | ----------------------- | -------- | ------------------------------------ | | `Authorization` | `Bearer YOUR_API_TOKEN` | Yes | Your API access token | | `x-rapiddocx-org-id` | `YOUR_ORGANIZATION_ID` | Yes | Your organization identifier | | `Content-Type` | `application/json` | Yes\* | Required for POST and PATCH requests | | `User-Agent` | `TurboDocx API Client` | No | Recommended for identification | ```http Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID Content-Type: application/json ``` :::caution Keep Your Tokens Secure Never expose your API tokens in client-side code or public repositories. Always use environment variables or a secrets manager to store credentials. ::: --- ## Endpoint 1: List Deliverables Retrieve a paginated list of deliverables in your organization, with optional filtering and sorting. ### Endpoint ```http GET https://api.turbodocx.com/v1/deliverable ``` ### Query Parameters | Parameter | Type | Required | Default | Description | | ---------- | ------- | -------- | ------- | ---------------------------------------- | | `limit` | Integer | No | 6 | Number of results per page (1–100) | | `offset` | Integer | No | 0 | Number of results to skip for pagination | | `query` | String | No | — | Search query to filter by name | | `showTags` | Boolean | No | false | Include tags in the response | ### Example Request ```bash curl -X GET "https://api.turbodocx.com/v1/deliverable?limit=10&offset=0&showTags=true" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" ``` ### Response ```json { "data": { "results": [ { "id": "39697685-ca00-43b8-92b8-7722544c574f", "name": "Employee Contract - John Smith", "description": "Employment contract for senior developer", "templateId": "0b1099cf-d7b9-41a4-822b-51b68fd4885a", "createdBy": "user-uuid-123", "email": "admin@company.com", "fileSize": 287456, "fileType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "defaultFont": "Arial", "fonts": [{ "name": "Arial", "usage": "body" }], "isActive": true, "createdOn": "2024-01-15T14:12:10.721Z", "updatedOn": "2024-01-15T14:13:45.724Z", "tags": [ { "id": "tag-uuid-1", "label": "hr", "isActive": true, "updatedOn": "2024-01-10T10:00:00.000Z", "createdOn": "2024-01-10T10:00:00.000Z", "createdBy": "user-uuid-123", "orgId": "org-uuid-123" }, { "id": "tag-uuid-2", "label": "contract", "isActive": true, "updatedOn": "2024-01-10T10:00:00.000Z", "createdOn": "2024-01-10T10:00:00.000Z", "createdBy": "user-uuid-123", "orgId": "org-uuid-123" } ] } ], "totalRecords": 42 } } ``` ### Response Fields | Field | Type | Description | | ----------------------- | ------- | ---------------------------------------------------------------- | | `data.results` | Array | Array of deliverable objects | | `data.totalRecords` | Integer | Total number of deliverables matching the query | | `results[].id` | String | Unique deliverable identifier (UUID) | | `results[].name` | String | Deliverable name | | `results[].description` | String | Deliverable description | | `results[].templateId` | String | Template used for generation | | `results[].createdBy` | String | User ID of the creator | | `results[].email` | String | Email of the creator | | `results[].fileSize` | Integer | File size in bytes | | `results[].fileType` | String | MIME type of the generated file | | `results[].defaultFont` | String | Default font used | | `results[].fonts` | JSON | Array of font objects with `name` and `usage` | | `results[].isActive` | Boolean | Whether the deliverable is active (not deleted) | | `results[].createdOn` | String | ISO 8601 creation timestamp | | `results[].updatedOn` | String | ISO 8601 last update timestamp | | `results[].tags` | Array | Tags (only when `showTags=true`) — see [Tag Object](#tag-object) | --- ## Endpoint 2: Create Deliverable Generate a new deliverable document by injecting variables into a template. This is the primary endpoint for document generation. ### Endpoint ```http POST https://api.turbodocx.com/v1/deliverable ``` :::important Authorization Required This endpoint requires one of these roles: **administrator**, **contributor**, or **user**. It also checks your organization's deliverable and storage limits. ::: ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------ | | `name` | String | **Yes** | Deliverable name (3–255 characters) | | `templateId` | String | **Yes** | Template ID to generate from | | `variables` | Array | **Yes** | Array of variable objects for substitution | | `description` | String | No | Description (up to 65,535 characters) | | `tags` | Array | No | Array of tag strings to associate | ### Variable Object Structure Each variable in the `variables` array represents a placeholder in the template to be substituted: | Field | Type | Required | Description | | --------------- | --------------- | -------- | ---------------------------------------------------------------------------------------- | | `placeholder` | String | Yes | Template placeholder (e.g., `{CompanyName}`) | | `text` | String | Yes\* | Value to inject (\* not required if `variableStack` is provided or `isDisabled` is true) | | `mimeType` | String | Yes | Content type: `text`, `html`, `image`, or `markdown` | | `isDisabled` | Boolean | No | Skip this variable during generation | | `subvariables` | Array | No | Nested sub-variables (for HTML content with dynamic placeholders) | | `variableStack` | Array or Object | No | Multiple instances (for repeating content) | | `aiPrompt` | String | No | AI prompt for content generation (max 16,000 chars) | ### Example Request ```bash curl -X POST "https://api.turbodocx.com/v1/deliverable" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -d '{ "templateId": "YOUR_TEMPLATE_ID", "name": "Employee Contract - John Smith", "description": "Employment contract for new senior developer", "variables": [ { "placeholder": "{EmployeeName}", "text": "John Smith", "mimeType": "text" }, { "placeholder": "{CompanyName}", "text": "TechCorp Solutions Inc.", "mimeType": "text" }, { "placeholder": "{JobTitle}", "text": "Senior Software Engineer", "mimeType": "text" }, { "mimeType": "html", "placeholder": "{ContactBlock}", "text": "Contact: {contactName}Phone: {contactPhone}", "subvariables": [ { "placeholder": "{contactName}", "text": "Jane Doe", "mimeType": "text" }, { "placeholder": "{contactPhone}", "text": "(555) 123-4567", "mimeType": "text" } ] } ], "tags": ["hr", "contract", "employee"] }' ``` ### Example with Variable Stacks (Repeating Content) Variable stacks allow you to inject multiple instances of a variable — useful for tables, lists, or repeating sections: ```json { "templateId": "YOUR_TEMPLATE_ID", "name": "Project Report", "variables": [ { "placeholder": "{ProjectPhase}", "mimeType": "html", "variableStack": { "0": { "text": "Phase 1: Assess environment", "mimeType": "html" }, "1": { "text": "Phase 2: Remediate findings", "mimeType": "html" }, "2": { "text": "Phase 3: Continue monitoring", "mimeType": "html" } } } ] } ``` ### Response ```json { "data": { "results": { "deliverable": { "id": "39697685-ca00-43b8-92b8-7722544c574f", "name": "Employee Contract - John Smith", "description": "Employment contract for new senior developer", "templateId": "0b1099cf-d7b9-41a4-822b-51b68fd4885a", "createdBy": "user-uuid-123", "createdOn": "2024-01-15T14:12:10.721Z", "updatedOn": "2024-01-15T14:12:10.721Z", "isActive": true, "defaultFont": "", "fonts": null } } } } ``` ### Response Fields | Field | Type | Description | | -------------------------- | ------- | ----------------------------------------------- | | `data.results.deliverable` | Object | The created deliverable object | | `deliverable.id` | String | Unique deliverable identifier (UUID) | | `deliverable.name` | String | Deliverable name | | `deliverable.description` | String | Deliverable description | | `deliverable.templateId` | String | Template used for generation | | `deliverable.createdBy` | String | User ID of the creator | | `deliverable.createdOn` | String | ISO 8601 creation timestamp | | `deliverable.updatedOn` | String | ISO 8601 last update timestamp | | `deliverable.isActive` | Boolean | Active status (always `true` on creation) | | `deliverable.defaultFont` | String | Default font (empty string if not specified) | | `deliverable.fonts` | JSON | Font metadata array (null if not yet generated) | :::info Request Cancellation If the client disconnects during generation, the server will detect the cancellation, clean up any partially created resources, and return a `499` status code. This prevents orphaned deliverables from consuming storage. ::: --- ## Endpoint 3: Get Deliverable Retrieve a single deliverable by ID, including its variables and optionally tags. ### Endpoint ```http GET https://api.turbodocx.com/v1/deliverable/:id ``` ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ---------------------------------------- | | `id` | String (UUID) | Yes | The unique identifier of the deliverable | ### Query Parameters | Parameter | Type | Required | Description | | ---------- | ------- | -------- | ---------------------------- | | `showTags` | Boolean | No | Include tags in the response | ### Example Request ```bash curl -X GET "https://api.turbodocx.com/v1/deliverable/39697685-ca00-43b8-92b8-7722544c574f?showTags=true" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" ``` ### Response ```json { "data": { "results": { "id": "39697685-ca00-43b8-92b8-7722544c574f", "name": "Employee Contract - John Smith", "description": "Employment contract for new senior developer", "templateId": "0b1099cf-d7b9-41a4-822b-51b68fd4885a", "templateName": "Employment Contract Template", "templateNotDeleted": true, "defaultFont": "Arial", "fonts": [{ "name": "Arial", "usage": "body" }], "email": "admin@company.com", "fileType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "isActive": true, "createdOn": "2024-01-15T14:12:10.721Z", "updatedOn": "2024-01-15T14:13:45.724Z", "variables": [ { "placeholder": "{EmployeeName}", "text": "John Smith", "mimeType": "text" } ], "tags": [ { "id": "tag-uuid-1", "label": "hr", "isActive": true, "updatedOn": "2024-01-10T10:00:00.000Z", "createdOn": "2024-01-10T10:00:00.000Z", "createdBy": "user-uuid-123", "orgId": "org-uuid-123" }, { "id": "tag-uuid-2", "label": "contract", "isActive": true, "updatedOn": "2024-01-10T10:00:00.000Z", "createdOn": "2024-01-10T10:00:00.000Z", "createdBy": "user-uuid-123", "orgId": "org-uuid-123" } ] } } } ``` ### Response Fields | Field | Type | Description | | ---------------------------- | ------- | ---------------------------------------------------------------- | | `results.id` | String | Unique deliverable identifier | | `results.name` | String | Deliverable name | | `results.description` | String | Deliverable description | | `results.templateId` | String | Template ID used for generation | | `results.templateName` | String | Template name | | `results.templateNotDeleted` | Boolean | Whether the source template still exists | | `results.defaultFont` | String | Default font used | | `results.fonts` | JSON | Array of font objects with `name` and `usage` | | `results.email` | String | Creator's email | | `results.fileType` | String | MIME type of the generated file | | `results.isActive` | Boolean | Active status | | `results.createdOn` | String | ISO 8601 creation timestamp | | `results.updatedOn` | String | ISO 8601 last update timestamp | | `results.variables` | Array | Parsed variable objects with values | | `results.tags` | Array | Tags (only when `showTags=true`) — see [Tag Object](#tag-object) | --- ## Endpoint 4: Update Deliverable Update an existing deliverable's metadata or tags. All fields are optional — only include the fields you want to change. ### Endpoint ```http PATCH https://api.turbodocx.com/v1/deliverable/:id ``` :::important Authorization Required This endpoint requires one of these roles: **administrator**, **contributor**, or **user**. ::: ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ---------------------------------------- | | `id` | String (UUID) | Yes | The unique identifier of the deliverable | ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------------------------- | | `name` | String | No | Updated name (3–255 characters) | | `description` | String | No | Updated description (up to 65,535 characters) | | `tags` | Array | No | Replace all tags (existing tags are removed first) | :::caution Tag Replacement When you provide `tags` in the update request, **all existing tags are replaced**. To add a tag, you must include the full list of desired tags. To remove all tags, pass an empty array. ::: ### Example Request ```bash curl -X PATCH "https://api.turbodocx.com/v1/deliverable/39697685-ca00-43b8-92b8-7722544c574f" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -d '{ "name": "Employee Contract - John Smith (Final)", "tags": ["hr", "contract", "finalized"] }' ``` ### Response ```json { "data": { "message": "Deliverable updated successfully", "deliverableId": "39697685-ca00-43b8-92b8-7722544c574f" } } ``` ### Response Fields | Field | Type | Description | | -------------------- | ------ | ----------------------------- | | `data.message` | String | Success confirmation message | | `data.deliverableId` | String | ID of the updated deliverable | --- ## Endpoint 5: Delete Deliverable Soft-delete a deliverable. The deliverable is marked as inactive and will no longer appear in list results, but its data is retained. ### Endpoint ```http DELETE https://api.turbodocx.com/v1/deliverable/:id ``` :::important Authorization Required This endpoint requires one of these roles: **administrator**, **contributor**, or **user**. ::: ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------------- | -------- | ---------------------------------------- | | `id` | String (UUID) | Yes | The unique identifier of the deliverable | ### Example Request ```bash curl -X DELETE "https://api.turbodocx.com/v1/deliverable/39697685-ca00-43b8-92b8-7722544c574f" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" ``` ### Response ```json { "data": { "message": "Deliverable deleted successfully", "deliverableId": "39697685-ca00-43b8-92b8-7722544c574f" } } ``` ### Response Fields | Field | Type | Description | | -------------------- | ------ | ----------------------------- | | `data.message` | String | Success confirmation message | | `data.deliverableId` | String | ID of the deleted deliverable | --- ## Endpoint 6: Download Source File (DOCX/PPTX) Download the original generated document file in its source format (DOCX or PPTX). ### Endpoint ```http GET https://api.turbodocx.com/v1/deliverable/file/:deliverableId ``` :::important Feature & Authorization Required This endpoint requires: - **Role**: administrator, contributor, or user - **License feature**: `hasFileDownload` must be enabled for your organization ::: ### Path Parameters | Parameter | Type | Required | Description | | --------------- | ------------- | -------- | ---------------------------------------- | | `deliverableId` | String (UUID) | Yes | The unique identifier of the deliverable | ### Example Request ```bash curl -X GET "https://api.turbodocx.com/v1/deliverable/file/39697685-ca00-43b8-92b8-7722544c574f" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ --output "employee-contract.docx" ``` ### Response Returns the binary content of the generated document with appropriate headers: ```http Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document Content-Disposition: attachment; filename="Employee Contract - John Smith.docx" ``` For PowerPoint templates, the content type will be: ```http Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation ``` --- ## Endpoint 7: Download PDF File Download the PDF version of a generated deliverable. ### Endpoint ```http GET https://api.turbodocx.com/v1/deliverable/file/pdf/:deliverableId ``` :::important Authorization Required This endpoint requires one of these roles: **administrator**, **contributor**, or **user**. ::: ### Path Parameters | Parameter | Type | Required | Description | | --------------- | ------------- | -------- | ---------------------------------------- | | `deliverableId` | String (UUID) | Yes | The unique identifier of the deliverable | ### Example Request ```bash curl -X GET "https://api.turbodocx.com/v1/deliverable/file/pdf/39697685-ca00-43b8-92b8-7722544c574f" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ --output "employee-contract.pdf" ``` ### Response Returns the binary PDF content: ```http Content-Type: application/pdf Content-Disposition: attachment; filename="Employee Contract - John Smith.pdf" ``` --- ## Tag Object When `showTags=true` is passed, tag arrays contain full tag objects with the following fields: | Field | Type | Description | | ----------- | ------- | ---------------------------------- | | `id` | String | Tag unique identifier (UUID) | | `label` | String | Tag display name | | `isActive` | Boolean | Whether the tag is active | | `updatedOn` | String | ISO 8601 last update timestamp | | `createdOn` | String | ISO 8601 creation timestamp | | `createdBy` | String | User ID of the tag creator | | `orgId` | String | Organization ID the tag belongs to | --- ## Best Practices ### Variable Preparation - Always match `placeholder` values exactly with your template placeholders (e.g., `{CompanyName}`) - Use `subvariables` for structured data within HTML content — the parent variable must have `mimeType: "html"` with HTML containing independent placeholder tokens, and each subvariable provides a value for one of those tokens - Use `variableStack` for repeating content (tables, list items) - Set `mimeType` to `html` when injecting formatted content ### Pagination - Use `limit` and `offset` for efficient data retrieval - The default `limit` is 6; set it up to 100 for larger result sets - Use `totalRecords` from the response to calculate total pages ### Tag Filtering - When passing `selectedTags`, all specified tags must match (AND logic) - Tags can be passed as a single UUID string or an array of UUIDs ### Error Handling - Always check the HTTP status code before parsing the response body - Implement retry logic with exponential backoff for `5xx` errors - Handle `499` (Client Closed Request) gracefully — the server has already cleaned up --- ## Error Handling ### HTTP Status Codes | Status | Description | Common Cause | | ------ | --------------------- | -------------------------------------------------- | | `200` | Success | Request completed successfully | | `400` | Bad Request | Validation error — check required fields and types | | `401` | Unauthorized | Invalid or missing Bearer token | | `403` | Forbidden | Missing role permission or invalid org ID | | `404` | Not Found | Deliverable or template ID does not exist | | `422` | Unprocessable Entity | Field constraint violation | | `429` | Too Many Requests | Rate limit exceeded — use exponential backoff | | `499` | Client Closed Request | Request cancelled during deliverable generation | | `500` | Internal Server Error | Server-side error — contact support | ### Error Response Format ```json { "error": "ValidationError", "message": "\"name\" length must be at least 3 characters long" } ``` ### Common Issues **Variable validation errors** - Ensure `text` is provided for each variable (unless `variableStack` is present or `isDisabled` is true) - Check that `mimeType` is one of: `text`, `html`, `image`, `markdown` - When using `html` mimeType with subvariables, ensure the parent `text` contains the subvariable placeholder tokens **Template not found** - Verify the `templateId` exists and has been fully processed (placeholders extracted) - Templates must be active (not deleted) **License limit exceeded** - Your organization may have reached its deliverable or storage quota - Contact your administrator to upgrade your plan **File download permission errors** - Source file download (`/v1/deliverable/file/:id`) requires the `hasFileDownload` feature to be enabled on your organization's license - Ensure your user role is administrator, contributor, or user (viewers cannot download) --- # Create Image Variable (Folder) --- # Create Tag --- # Create Webhook --- # Delete Tags (by IDs) --- # Delete Template --- # Delete Variables (by IDs) --- # Delete Webhook --- # Edit Template Metadata --- # Extract Template Placeholders and Generate Preview --- # Get Template by ID --- # Get Templates and Folders --- # Get Webhook Stats --- # Get Webhook --- # List Webhook Deliveries --- # Notify Webhook --- # Read Tag --- # Read Variables (Folder) --- # Regenerate Webhook Secret --- # Replay Webhook Delivery --- # Test Webhook --- ## TurboDocx API Documentation Version: 1.0.0 TurboDocx API Documentation Welcome to the TurboDocx API Documentation. This comprehensive guide is designed to provide developers with all the information required to interact with the TurboDocx platform programmatically. Whether you're looking to automate document creation, manage your templates, or integrate TurboDocx features into your application, you'll find the necessary endpoints, parameters, and examples here. ## **Getting started guide** Before diving into the specifics of the API, ensure that you have obtained the necessary Access Token and have the correct permissions to access the desired resources. Keep your Access Token secure and do not expose them in publicly accessible areas such as GitHub, client-side code, etc. **The Authentication and Organization ID is a variable set at the root of the collection.** ## Base URL All API requests should be made to the base URL provided upon your API key registration. Typically, it looks like this: ``` typescript https://api.turbodocx.com ``` ## Authentication TurboDocx is working on it's API Key flows, and for the time being, we recommend grabbing the access token used from your user's accounts to leverage our APIs. You can find an example on how to get your access token in the `Blueprint` folder. To use the Access Token, use the following under the `Authorization Header` ``` typescript Authorization: Bearer YOUR_ACCESS_TOKEN ``` ### Authentication error response If an Access Token is missing, malformed, or invalid, you will receive an HTTP 401 Unauthorized response code. ### **Need some help?** In case you have questions, go through our tutorials, click through out UI and network trace it, or visit our [Discord](https://discord.gg/NYKwz4BcpX) to review topics, ask questions, and learn from others. --- # Update Tag --- # Update Variable (by ID) --- # Update Webhook --- # Upload Template with Optional Default Values --- ## Configuring SharePoint and OneDrive # SharePoint and OneDrive Integration The SharePoint integration in TurboDocx brings powerful document management capabilities to your organization's SharePoint environment. This seamless integration allows for efficient importing of templates from different SharePoint sites and User's OneDrive and effortless exporting of deliverables back to SharePoint. ## Configuring SharePoint and OneDrive for Business with TurboDocx This guide will provide you step-by-step instructions on creating a SharePoint integration that can connect to your SharePoint sites as well as OneDrive. We will be using the Microsoft Graph API to access the SharePoint and OneDrive resources. Pre-requisites: An Office365 Tenant with SharePoint and OneDrive for Business Enabled Access to the Admin/Azure portal and the ability to register an application. ## Step 1 : Register your application in Entra ID (Azure Active Directory): 1. Sign in to the SharePoint Dashboard (or Azure Admin Portal) and click on "Entra ID (Azure Active Directory)" in the respective menu. Click on the "App Registrations and then click on "New registration" ![Entra ID App Registrations page with New Registration button highlighted](/img/sharepoint_and_onedrive/app-reg.png) 2. Enter a name for the application such as "TurboDocx Sharepoint integration", select the appropriate account type, and select Single Page Application with the redirect uri: "https://app.turbodocx.com" ![Register an application form with name, account type, and redirect URI fields](/img/sharepoint_and_onedrive/Register_Application.png) 3. Click on the "Register Button" to create the application. 4. Note down the Application (Client) ID and the Directory (tenant) ID, as you will need them later. ![Alt text](/img/sharepoint_and_onedrive/Get_Site_ID_and_Client_ID.png) ## Step 2 : Configure API Permissions: 1. In the "App registrations" menu, click on "API" permissions" in the left-hand menu. 2. Click on "Add a permission" 3. Click "Microsoft Graph" and then "Delegated permissions" ![Alt text](/img/sharepoint_and_onedrive/Click_Delegated_Permissions.png) 4. Search for the following permissions and add them: Sites.Read.All - This permission allows TurboDocx to read all site collections, sites, lists, and list items in SharePoint on behalf of the signed-in user. Please note - TurboDocx does not make API calls to your SharePoint site without user interaction. This will mirror whatever permissions the user has in the SharePoint site. Files.ReadWrite.All - This permission allows TurboDocx to read and write (create, edit, and delete) all files in OneDrive for Business, SharePoint document libraries, and Microsoft Teams files on behalf of the signed-in user. With this permission, TurboDocx can access and manage files stored in OneDrive and SharePoint, including creating new files, updating existing files, moving or deleting files, and even sharing files with others. :::note TurboDocx does not make API calls to your SharePoint site without user interaction.Keep in mind that these are delegated permissions, which means TurboDocx can access these resources on behalf of the signed-in user. The level of access TurboDocx will have depends on the user's actual permissions in SharePoint and OneDrive. ::: ![Alt text](/img/sharepoint_and_onedrive/Graph_Permissions.png) 5. After clicking save, navigate back to "Add Permission" and click SharePoint. Click "SharePoint" and then "Delegated Permissions" ![Alt text](/img/sharepoint_and_onedrive/SharePoint_Delegated_Permissions.png) 6. Search for the following permissions and add them: **AllSites.Read** - This permission allows TurboDocx to read all site collections, sites, lists, and list items in SharePoint on behalf of the signed-in user. With this permission, TurboDocx can access and retrieve information about the SharePoint site content, such as document libraries, lists, and list items. **AllSites.Write** - This permission allows TurboDocx to write to all site collections, sites, lists, and list items in SharePoint on behalf of the signed-in user. With this permission, TurboDocx can create, edit, and delete SharePoint site content. This allows TurboDocx to save Deliverables to your SharePoint site. **MyFiles.Read** - This permission allows TurboDocx to read the signed-in user's files in OneDrive for Business. With this permission, TurboDocx can access and retrieve information about the user's files, such as file names, file types, and file metadata. MyFiles.Write - This permission allows TurboDocx to write to the signed-in user's files in OneDrive for Business. With this permission, TurboDocx can create, edit, and delete the user's files :::note TurboDocx does not make API calls to your SharePoint site without user interaction. This user's access will mirror whatever permissions the user has in the SharePoint site. Keep in mind that these are delegated permissions, which means TurboDocx can access these resources on behalf of the signed-in user. The level of access TurboDocx will have depends on the user's actual permissions in SharePoint and OneDrive. ::: ![Alt text](/img/sharepoint_and_onedrive/SharePoint_Permissions_Select.png) 7. The finished permission sets should look like the below: ![Alt text](/img/sharepoint_and_onedrive/Finished_Permission_Set.png) ## Step 3: Determine the default site that TurboDocx will open and get the path 1. The easiest way to do this is to navigate to your Site and find the path you want users to see first. 2. Take note of the URL path and notate it for future use. In this case it is "DocumentationTeam" ![Alt text](/img/sharepoint_and_onedrive/GetSiteName.png) ## Step 4: Login to your TurboDocx Tenant and go to Tenant Settings 1. As an admin within your TurboDocx tenant, navigate to the settings tab on the left-hand side-nav and click "Tenant Settings" in the top right corner. 2. If Hide SharePoint in the UI is selected, unselect this to get the SharePoint configuration button. 3. Click "Configure SharePoint" ![Alt text](/img/sharepoint_and_onedrive/Configure_Sharepoint_button.png) 4. Fill out the following fields with the information you have noted from previous steps and click "Save": Tenant Name - This is the name of the SharePoint tenant are connecting to. This can be found by looking in the browser bar and it should resemble "TenantName.SharePoint.com" Site Name - This is the name of the default site your users will land on when first opening the SharePoint file picker. This is noted from the previous step. **Client ID** - This is the Application (client) ID noted from the previous step in the EntraID (AzureAD) console. **Tenant ID** - This is the Directory (tenant) ID noted from the previous step in the EntraID (AzureAD) console. ![Alt text](/img/sharepoint_and_onedrive/SharePoint_Configuration_in_TurboDocx.png) 5. Test importing and Exporting templates using the steps noted in the following sections. ## Importing Templates One of the primary benefits of the SharePoint integration in TurboDocx is the ability to directly import templates from your SharePoint document libraries. This feature removes the need to manually upload templates to TurboDocx, streamlining the process and saving you both time and effort. By simply connecting your SharePoint tenant to TurboDocx, you can effortlessly access and import your templates. The integration offers a seamless browsing experience, enabling you to navigate through your SharePoint document libraries and folders to choose the desired templates for import. Whether you have pre-existing templates or wish to import templates created using other applications such as Microsoft Word or Office 365, the SharePoint integration simplifies the process. Upon selecting the templates, TurboDocx securely imports them into your TurboDocx account, ready for customization and document generation. This ensures that your templates are readily available within TurboDocx, eliminating the need for manual file transfers or duplicate storage. ![Alt text](/img/sharepoint_and_onedrive/Import_with_SharePoint.png) ## Exporting Deliverables The SharePoint integration also enables you to export deliverables generated by TurboDocx back to your SharePoint and One Drive for business accounts. Once you have personalized and generated documents based on your templates, you can seamlessly export them to your SharePoint storage for easy access and sharing. Exporting deliverables to SharePoint and One Drive for Business ensures that your documents are stored securely in the cloud, providing a reliable backup and making them accessible from any device with an internet connection. By leveraging the familiar SharePoint interface, you can organize your deliverables into folders, share them with collaborators, and control access permissions as needed. --- # SCIM User Provisioning TurboDocx offers enterprise customers the ability to integrate System for Cross-domain Identity Management (SCIM) provisioning with their platforms, enhancing user and group management across your organization. SCIM provisioning automates the user lifecycle process, ensuring that user accounts are created, updated, and deactivated in a synchronized manner across various applications and services. TurboDocx's SCIM implementation supports standard protocols, allowing for seamless integration with a wide range of identity providers (IdPs) that support SCIM standards. This ensures that TurboDocx can work effectively with your organization's existing identity management solutions. ## Supported Identity Providers Some of the major identity providers that support SCIM and can be integrated with TurboDocx include: - Okta - Microsoft Azure Active Directory (Azure AD) - and more Integrating SCIM provisioning with TurboDocx allows your IT teams to manage user identities and access rights efficiently from a central location, reducing administrative overhead and enhancing security. The automation of user account management provided by SCIM also helps in reducing the possibilities of human error during the account setup and maintenance phases. To set up SCIM provisioning with TurboDocx, please reach out to our support team. They will provide detailed guidance through the necessary steps and offer support throughout the setup process. Once SCIM provisioning is implemented, user account management will be automated according to the configurations set in your identity provider, streamlining the process and ensuring a consistent and secure user experience across all enterprise applications. --- # Single Sign-On (SSO) Configuration TurboDocx offers enterprise customers the ability to integrate their Single Sign-On (SSO) solution with the platform, streamlining user authentication and providing a secure and convenient way to access TurboDocx. Our SSO integration supports both Security Assertion Markup Language (SAML) and OpenID Connect (OIDC) protocols, which means we can seamlessly integrate with a wide variety of SSO providers. Some of the major SSO providers that we can integrate with include: - Okta - EntraID - Microsoft Azure Active Directory (Azure AD) - OneLogin - Auth0 - Google Cloud Identity - Ping Identity - Centrify - ADFS (Active Directory Federation Services) and more By integrating TurboDocx with your SSO solution, you can leverage the benefits of centralized user management, improved security, and simplified access to TurboDocx for your organization. This integration supports various directories, ensuring that you can maintain control over user authentication and access while still providing a seamless experience for your users. To set up the SSO integration, please contact our support team, who will guide you through the necessary steps and provide any assistance required during the process. Once the integration is complete, your users will be able to access TurboDocx through your organization's SSO solution, ensuring a secure and unified authentication experience across all your enterprise applications. --- ## ConnectWise PSA Integration # Automate Documents from ConnectWise PSA Data TurboDocx integrates directly with your ConnectWise PSA environment to eliminate manual copy-paste and transform your real data into documents, proposals, and presentations. Pull in data from companies, tickets, configurations, and more — and generate polished deliverables instantly. ## What You Can Create * **📄 Statements of Work (SOWs)**: Pull in configurations, timelines, and tasks from project boards * **🗌 Ticket Summaries**: Turn tickets into polished summaries for clients * **💼 Client Reports**: Generate reports using real company data and services * **🧠 Technical Docs**: Document configurations, assets, or custom fields from ConnectWise * **🚰 Implementation Guides**: Auto-generate guides from projects and recurring service tickets * **📊 Executive Briefs**: Create management-facing updates using ConnectWise reporting fields ## Before You Begin :::tip Not a PSA Admin? Don’t worry — this guide walks you through step-by-step. You don’t have to be a PSA wizard. If you can follow a recipe, you can follow this. 🍻 ::: You'll need: * Admin access to your ConnectWise PSA account * Your ConnectWise tenant endpoint URL (e.g., `https://yourcompany.connectwise.com`) * 5 minutes of time and a steady Wi-Fi signal 🚀 After completing the setup, you'll have: * **Tenant Endpoint**: Your ConnectWise server URL (e.g., `https://yourcompany.connectwise.com`) * **Company Name**: Your ConnectWise company identifier (Site or Company ID) * **Public Key**: API public key * **Private Key**: API private key * **Client ID**: ConnectWise client application ID (you can get this from [https://developer.connectwise.com/ClientID](https://developer.connectwise.com/ClientID)) :::tip Quick PSA Primer ConnectWise PSA uses "API Members". You'll create a special user with read-only API access for TurboDocx. ::: ## Step 1: Create a Security Role and API Member ### A. Create a Read-Only Security Role 1. Log into the ConnectWise PSA dashboard 2. Navigate to **System** in the left navigation 3. Click on **Security Roles** ![Creating Security Role](/img/connectwise_integration/SecurityRoleCreation.png) 4. Press the **"+" (plus)** button in the top-left corner to create a new role 5. Set the **Role ID** to `TurboDocxIntegration` 6. Configure the following permissions with **Inquire Level** access: #### Company * **Company Maintenance** – Inquire All * **Company/Contact Group Maintenance** – Inquire All * **Configurations** – Inquire All * **Contacts** – Inquire All * **CRM/Sales Activities** – Inquire All * **Manage Attachments** – Inquire All * **Notes** – Inquire All * **Reports** – Inquire All * **Team Members** – Inquire All #### Finance * **Agreement Invoicing** – Inquire All * **Agreement Sales** – Inquire All * **Agreements** – Inquire All * **Billing View Time** – Inquire All * **Invoicing** – Inquire All * **Reports** – Inquire All #### Projects * **All Permissions** – Inquire All #### Sales * **Closed Opportunity** – Inquire All * **Opportunity** – Inquire All * **Sales Orders** – Inquire All * **Sales Orders Finance** – Inquire All #### Service Desk * **Change Approvals** – Inquire All * **Close Service Tickets** – Inquire All * **Merge Tickets** – Inquire All * **Reports** – Inquire All * **Resource Scheduling** – Inquire All * **Service Tickets** – Inquire All * **Service Tickets – Finance** – Inquire All #### System * **Member Maintenance** – Inquire All * **My Company** – Inquire All #### Time and Expense * **Time Entry** – Inquire 7. Save the new role :::tip Best Practice The TurboDocx integration uses **read-only access** (Inquire level) to retrieve data from ConnectWise without making any modifications to your system. ::: ### B. Add API Member 1. Go to **System → Members** ![Navigate to ConnectWise Members](/img/connectwise_integration/NavigateToCWMembers.png) 2. Click the **API Members** tab ![Click API Members Tab](/img/connectwise_integration/ClickAPIMembers.png) 3. Click **"+" (Add New Member)** ![Click Plus to Add New API Member](/img/connectwise_integration/ClickPlusOnAPIMembers.png) 4. Fill out the form: ![Fill Out API Member Form](/img/connectwise_integration/FillOutAPIMember.png) * **Member ID**: `TurboDocx` * **Role ID**: `TurboDocxIntegration` * **Level**: Corporate * **Name/Email**: `Your CW Admin`, `CWAdmin@yourcompany.com` * **Location & Business Unit**: Required fields ### C. Generate API Keys 1. Scroll to **API Keys** at the bottom ![Click API Keys Section](/img/connectwise_integration/ClickAPIKeys.png) 2. Click **"+"**, name it `TurboDocx Key` 3. Save and securely store the **Public** and **Private** API keys ## Step 2: Configure TurboDocx 1. Log into your TurboDocx dashboard 2. Go to **Settings → Organization Settings** 3. Find **ConnectWise PSA Integration** and click **Configure** ![TurboDocx ConnectWise Configuration Fields](/img/connectwise_integration/TurboDocxFields.png) ### Enter Required Information * **Tenant Endpoint**: e.g. `https://yourcompany.connectwise.com` (or select from regional options) * **Company Name**: Your ConnectWise Site or Company ID * **Public Key**: The public API key generated in Step 2 * **Private Key**: The private API key generated in Step 2 * **Client ID**: Your ConnectWise client application ID Click **Save Settings**, then **Test Connection** to validate access. :::tip What Testing the Connection Does It validates your credentials and pulls your ticket types, boards, statuses, configurations, and custom fields for use in documents. ::: ## Step 3: Generate Documents from ConnectWise 1. Open the **Document Generator** in TurboDocx 2. Click **New Document** 3. Choose a template 4. Under **Change Source**, select **ConnectWise PSA** ![Click on ConnectWise PSA Agent in App Library](/img/connectwise_integration/ClickonConnectWisePSAAgentInAppLibrary.png) 5. Choose the object type: Tickets, Projects, Companies, etc. 6. Select records you want to use ![Access to Records in TurboDocx](/img/connectwise_integration/AccessToRecordsCWInTurbo.png) ### Example Prompts * “Create an SOW for this project with timelines, configurations, and tasks.” * “Generate a config documentation PDF for selected assets.” * “Summarize all open tickets for this company.” ## Troubleshooting ### "Invalid Credentials" Error * Check API keys, Company Name (Site/Company ID), and Tenant Endpoint * Confirm role permissions and member status * Verify Client ID is correct ### "Connection Failed" Error * Click **Test Connection** to verify settings * Ensure your PSA instance has data to access * Check that your tenant endpoint URL is correct ### “Permission Denied” * Review the API member’s security role permissions :::tip Still Stuck? Take a screenshot, note the step, and send it to our support team. ::: ## Security and Privacy * 🔐 Read-only access only * 📦 Encrypted data transfers * 🔒 API keys are not stored permanently * 🔍 Access can be revoked at any time in ConnectWise ## Final Thoughts You're ready to: * Generate project documents with no formatting headaches * Turn tickets and configs into polished deliverables * Automate technical documentation and reporting :::tip Last Tip Clean ConnectWise data = clean, impressive documents. Keep your CRM tidy! ::: --- ## Appendix: Permission Requirements Explained Understanding why TurboDocx requires specific Inquire permissions helps ensure proper setup and security compliance. ### Company Permissions **Company Maintenance** - **Inquire All** - Without this, TurboDocx can't query what companies exist for document generation **Company/Contact Group Maintenance** - **Inquire All** - Provides access to company and contact grouping data for organizational document generation **Configurations** - **Inquire All** - Enables access to company configuration data for technical documentation and reports **Contacts** - **Inquire All** - Essential for retrieving contact data to populate documents with recipient information **CRM/Sales Activities** - **Inquire All** - Without this, TurboDocx can't access sales activity data for inclusion in reports and proposals **Manage Attachments** - **Inquire All** - Required to access existing attachments for document templating **Notes** - **Inquire All** - Allows TurboDocx to review company and contact notes for inclusion in generated documents **Reports** - **Inquire All** - Provides access to existing report data for comprehensive document generation **Team Members** - **Inquire All** - Required to identify team members for assignment and attribution in generated documents ### Finance Permissions **Agreement Invoicing** - **Inquire All** - Provides access to agreement invoicing data for generating billing documents and financial reports **Agreement Sales** - **Inquire All** - Enables access to agreement sales data for creating sales reports and contract documentation **Agreements** - **Inquire All** - Provides access to agreement data for generating contracts, SOWs, and billing documents **Billing View Time** - **Inquire All** - Allows access to time tracking data for creating detailed billing reports and invoices **Invoicing** - **Inquire All** - Enables access to invoicing data for generating financial reports and billing summaries **Reports** - **Inquire All** - Provides access to financial reporting data for comprehensive document generation ### Project Management Permissions **All Permissions** - **Inquire All** - Comprehensive access to all project data enables generation of project documentation, status reports, and project summaries ### Sales Permissions **Closed Opportunity** - **Inquire All** - Provides access to closed opportunity data for won/lost analysis and historical sales reporting **Opportunity** - **Inquire All** - Essential for accessing active opportunity data to generate proposals, quotes, and sales reports **Sales Orders** - **Inquire All** - Enables access to sales order data for order documentation and fulfillment tracking **Sales Orders Finance** - **Inquire All** - Provides access to financial aspects of sales orders for billing and revenue reporting ### Service Desk Permissions **Change Approvals** - **Inquire All** - Provides access to change approval data for compliance and change management reporting **Close Service Tickets** - **Inquire All** - Allows access to completed ticket data for historical reporting and closure documentation **Merge Tickets** - **Inquire All** - Enables access to merged ticket data for comprehensive service history tracking **Reports** - **Inquire All** - Provides access to service desk reporting data for comprehensive document generation **Resource Scheduling** - **Inquire All** - Provides access to scheduling data for resource allocation reports and planning documents **Service Tickets** - **Inquire All** - Essential for accessing ticket data to generate service reports and client summaries **Service Tickets – Finance** - **Inquire All** - Enables access to ticket financial data for billing reports and cost analysis ### System Permissions **Member Maintenance** - **Inquire All** - Required to identify team members for assignment and attribution in generated documents **My Company** - **Inquire All** - Provides access to company configuration and branding information for document customization ### Time and Expense Permissions **Time Entry** - **Inquire** - Enables access to time tracking data for billing reports and project summaries :::note Security Note All permissions are set at the **Inquire** level to ensure TurboDocx operates with read-only access, maintaining data security while enabling document generation functionality. ::: --- *May your configs be documented, your projects delivered, and your SOWs write themselves — automatically.* --- # Fireflies AI Integration Transform your meeting recordings into professional documents automatically. TurboDocx's Fireflies integration eliminates manual note-taking by converting meeting transcripts into polished deliverables, streamlining your post-meeting workflow. ## What You Can Create - **📄 Meeting Summaries**: Convert discussions into formatted meeting minutes and action items - **📊 Client Presentations**: Turn discovery calls into compelling presentation decks - **📋 Project Proposals**: Use requirement gathering sessions to create detailed proposals - **📝 Follow-up Reports**: Generate comprehensive meeting reports with key takeaways - **💼 Sales Materials**: Transform prospect calls into customized sales presentations - **🔄 Automated Workflows**: Connect meeting transcripts directly to your document templates ## Before You Begin To use the Fireflies integration, you'll need: - A Fireflies account with recorded meetings and transcripts - Administrative access to your Fireflies account to generate API keys - Meeting recordings stored in Fireflies with transcription enabled :::tip If you're not sure whether you have transcription enabled, check your Fireflies settings or look for transcript availability in your recorded meetings. ::: ## Step 1: Configuring Fireflies.ai ### Get Your API Key 1. Log in to [app.fireflies.ai](https://app.fireflies.ai) 2. Navigate to the **"Integrations"** section in your dashboard ![Fireflies Integration Section](/img/fireflies_integration/click_on_integration.png) 3. Click on **"Fireflies API"** ![Fireflies API Integration](/img/fireflies_integration/click_on_fireflies.png) 4. Click on **"Get API KEY"** ![Get API Key](/img/fireflies_integration/click_on_get_api_key.png) 5. **Copy your API key** - You'll need this value when configuring TurboDocx ![Copy API Key](/img/fireflies_integration/copy_api_key.png) :::tip Keep Your API Key Safe Your API key is like a password - never share it publicly or include it in emails. TurboDocx will store it securely once you enter it. ::: ## Step 2: Configuring TurboDocx ### 🔧 How to Configure Fireflies in Organization Settings 1. Go to **Settings** ![Go to Settings](/img/zoom_integration/GoToSettings.png) 2. Click on **Organization Settings** ![Go to Organization Settings](/img/zoom_integration/GoToOrganizationSettings.png) 3. Scroll down to the **Fireflies** section 4. Click **Configure Fireflies** ![Click Configure Fireflies](/img/fireflies_integration/configure_fireflies.png) 5. A Fireflies Configuration pop-up will appear 6. Take the **API Key** you obtained earlier, paste it into the appropriate field, and click **Save Configuration** ![Save API Key](/img/fireflies_integration/save_api_key.png) ## Step 3: Using Your Fireflies Integration Congratulations! Your Fireflies integration is now ready to use. Here's how to access your meeting transcripts and use them in your documents. ### Accessing Meeting Transcripts 1. Click the source dropdown and click **"Change Source"** 2. Go to the **App Library** tab 3. Click **"Transcript Providers"** ![Meeting Providers](/img/zoom_integration/MeetingProviders.png) 4. Click **"Fireflies"** and then you should be able to click the transcript 5. You'll see a list of your recent meeting recordings with transcripts ![Transcripts Shown](/img/fireflies_integration/transcripts.png) ## Troubleshooting If you're having trouble with your Fireflies integration, here are some common issues and their solutions. ### "No Meetings Found" - **Cause**: No recorded meetings with transcripts available - **Solution**: Ensure your meetings are recorded in Fireflies and have transcription enabled ### "Authentication Failed" - **Cause**: Incorrect API key or expired authentication - **Solution**: - Double-check your API key - Try re-authenticating by entering your API key again :::tip Getting Help If you continue to experience issues: - Verify all steps in this guide have been completed - Check that your Fireflies account has meeting recordings with transcripts - Ensure you have the correct API key from your Fireflies account - Contact your TurboDocx support team for assistance ::: ## Security and Privacy Your data security and privacy are important to us. Here's how your information is protected when using the Fireflies integration. ### How Your Data is Protected - **Secure Authentication**: TurboDocx uses API key authentication for secure access - **Read-Only Access**: We only read your data as per your request in the flow - we never modify your Fireflies data - **Encrypted Storage**: Your API key is stored encrypted in our secure systems - **Encrypted Transmission**: All data transfers are encrypted using industry-standard protocols ## Best Practices ### For Optimal Results - **Enable Transcription**: Always enable transcription when recording meetings in Fireflies - **Use Clear Audio**: Ensure good audio quality for more accurate transcripts - **Descriptive Meeting Names**: Use clear, descriptive names for your meetings to easily identify them later - **Regular Cleanup**: Periodically review and organize your meeting recordings ### Meeting Recording Tips - Start recording at the beginning of important meetings - Speak clearly and avoid overlapping conversations - Use a good quality microphone when possible - Consider recording in a quiet environment ## Finished Congratulations on setting up your Fireflies integration! You can now seamlessly import meeting transcripts and use them to create professional documents with TurboDocx. --- # Google Drive Integration The Google Drive integration in TurboDocx provides a seamless and efficient way to manage your document generation process. With this integration, you can import templates from Google Drive and export deliverables back to your Google Drive environment, streamlining your workflow and enhancing collaboration. Let's delve into the details of the Google Drive integration and explore its features and benefits. ## Importing Templates One of the key advantages of the Google Drive integration is the ability to import templates directly from your Google Drive storage. This functionality eliminates the need to manually upload templates to TurboDocx, saving you time and effort. By simply connecting your Google Drive account to TurboDocx, you can access and import your templates with ease. The integration provides a seamless browsing experience, allowing you to navigate through your Google Drive folders and select the desired templates for import. Whether you have pre-existing templates or want to import templates created using other applications such as Google Docs or Microsoft Word, the Google Drive integration simplifies the process. Upon selecting the templates, TurboDocx securely imports them into your TurboDocx account, ready for customization and document generation. This ensures that your templates are readily available within TurboDocx, eliminating the need for manual file transfers or duplicate storage. ![TurboDocx file picker browsing Google Drive folders to import templates](https://image.typedream.com/cdn-cgi/image/width=3840,format=auto,fit=scale-down,quality=100/https://api.typedream.com/v0/document/public/de39171b-a5c9-49c5-bd9c-c2dfd5d632a2/2P7GcbCddIpTtDMDZpQltzuC1ff_Import_from_Google_Drive.png) ### **Exporting Deliverables** The Google Drive integration also enables you to export deliverables generated by TurboDocx back to your Google Drive. Once you have personalized and generated documents based on your templates, you can seamlessly export them to your Google Drive storage for easy access and sharing. Exporting deliverables to Google Drive ensures that your documents are stored securely in the cloud, providing a reliable backup and making them accessible from any device with an internet connection. By leveraging the familiar Google Drive interface, you can organize your deliverables into folders, share them with collaborators, and control access permissions as needed. ![TurboDocx export dialog saving a generated deliverable to Google Drive](https://image.typedream.com/cdn-cgi/image/width=3840,format=auto,fit=scale-down,quality=100/https://api.typedream.com/v0/document/public/de39171b-a5c9-49c5-bd9c-c2dfd5d632a2/2P7LjJqYUUFwjrpmKHYZreunEVm_Export_to_Google_Drive.png) --- ## HubSpot Integration # Turn Your HubSpot Data into Professional Documents & Presentations Say goodbye to copy-pasting customer information! TurboDocx's HubSpot integration automatically pulls your real CRM data to create personalized documents, proposals, and presentations. No more "John Doe" placeholder text — use actual customer names, deals, and details. ## What You Can Create - **📊 Client Proposals**: Use real deal data to create compelling, personalized proposals - **📄 Contact Reports**: Generate comprehensive reports with actual customer information - **📋 Meeting Summaries**: Create professional meeting notes using your HubSpot data - **💼 Sales Presentations**: Build custom presentations with live customer data - **📝 Follow-up Documents**: Generate personalized follow-ups using contact details - **🔄 Automated Reports**: Create recurring reports with fresh data from HubSpot ## Before You Begin :::tip For Our Technology-Shy Friends Don't worry! We've made this guide so detailed that any team member can follow it. Think of it as cooking instructions, but for software — and just as tasty! 😄 ::: To use the HubSpot integration, you'll need: - A HubSpot account with Private App Access (free or paid — we don't judge!) - Administrative access to create a HubSpot app - About 10 minutes and a cup of coffee ☕ - This guide (which you're already reading — you're ahead of the game!) :::tip Quick note This process involves creating something called a "private app" in HubSpot. Think of it as giving TurboDocx secure access to your HubSpot data — and only the data you want to share. ::: ## Step 1: Creating Your HubSpot Private App The first step is creating a HubSpot private app. Think of this as getting a library card — it gives TurboDocx permission to temporarily read your HubSpot data. ### Navigate to HubSpot Developer Settings 1. **Open your web browser** and go to [hubspot.com](https://hubspot.com) - Use Chrome, Firefox, Safari, or Edge 2. **Log into your HubSpot account** - Use your normal email and password - If you forgot your password, there's a "Forgot Password?" link (we've all been there!) ![HubSpot Login](/img/hubspot-integration/hubspot-dashboard.png) 3. **Find the Settings gear** in the top right corner - It looks like a little wheel with teeth ⚙️ - If you can't find it, it's probably hiding next to your profile picture ![HubSpot Settings Gear](/img/hubspot-integration/hubspot-settings.png) 4. **Click on the Settings gear** - A menu will appear ### Navigate to Integrations 1. **Look for "Integrations"** in the left sidebar - It's usually about halfway down the list - If you don't see it, try scrolling down slowly (no need to rush!) ![Navigate to Integrations](/img/hubspot-integration/create-private-app-steps.png) 2. **Click on "Integrations"** - The menu will expand to show more options 3. **Find and click "Private Apps"** - It might be hiding under the Integrations section - Think of it as a secret menu item at your favorite restaurant ![Navigate to Integrations](/img/hubspot-integration/create-private-app-steps.png) ### Create Your Private App 1. **Click "Create a private app"** - Usually a big, friendly blue button - If there's no button, you might need admin permissions (time to find your BFF at your IT department!) ![Navigate to Integrations](/img/hubspot-integration/create-private-app-steps.png) 2. **Fill in the basic information**: - **App name**: Type something memorable like "TurboDocx Integration" or "TurboDocx Document Generator" - **Description**: Add a simple description like "This connects my HubSpot data to TurboDocx for document and presentation generation" - **Logo**: Optional - it's up to you! ![App Basic Info](/img/hubspot-integration/app-logo-description.png) :::tip Pro Tip Choose an app name you'll remember six months from now. "App123" might seem clever today, but future you will not be amused! 😏 ::: ### Configure App Permissions (The Important Part!) 3. **Click on the "Scopes" tab** - This is where you tell HubSpot what data TurboDocx can access - It’s like setting clear boundaries — you choose exactly what TurboDocx can read from your CRM. ![Scopes Tab](/img/hubspot-integration/add-scope-step.png) 4. **Add the required scopes** (permissions) - Each scope is like giving TurboDocx permission to read a specific type of data ![Scopes Tab](/img/hubspot-integration/scope-adding-step.png) :::tip Attention to Detail Required You'll be adding about 50 different permissions. It's like checking off a very long grocery list — but necessary for the feast ahead! 🛒 ::: **Here's the complete list of scopes to add** (copy each one EXACTLY): **CRM Lists:** - `crm.lists.read` **CRM Objects (the big list):** - `crm.objects.appointments.read` - `crm.objects.carts.read` - `crm.objects.commercepayments.read` - `crm.objects.companies.read` - `crm.objects.contacts.read` - `crm.objects.courses.read` - `crm.objects.custom.read` - `crm.objects.deals.read` - `crm.objects.feedback_submissions.read` - `crm.objects.goals.read` - `crm.objects.invoices.read` - `crm.objects.leads.read` - `crm.objects.line_items.read` - `crm.objects.listings.read` - `crm.objects.marketing_events.read` - `crm.objects.orders.read` - `crm.objects.owners.read` - `crm.objects.partner-clients.read` - `crm.objects.partner-services.read` - `crm.objects.products.read` - `crm.objects.quotes.read` - `crm.objects.services.read` - `crm.objects.subscriptions.read` - `crm.objects.users.read` **CRM Pipelines:** - `crm.pipelines.orders.read` **CRM Schemas:** - `crm.schemas.appointments.read` - `crm.schemas.carts.read` - `crm.schemas.commercepayments.read` - `crm.schemas.companies.read` - `crm.schemas.contacts.read` - `crm.schemas.courses.read` - `crm.schemas.custom.read` - `crm.schemas.deals.read` - `crm.schemas.invoices.read` - `crm.schemas.line_items.read` - `crm.schemas.listings.read` - `crm.schemas.orders.read` - `crm.schemas.quotes.read` - `crm.schemas.services.read` - `crm.schemas.subscriptions.read` **Sales & Communication:** - `sales-email-read` **Scheduler:** - `scheduler.meetings.meeting-link.read` **Settings:** - `settings.currencies.read` - `settings.security.security_health.read` - `settings.users.read` - `settings.users.teams.read` **Tax:** - `tax_rates.read` ### How to Add Each Scope **For each scope in the list above:** 1. **Find the search box** (it might say "Search for scopes") 2. **Click inside the search box** 3. **Type the scope name exactly** (every dot and dash matters!) 4. **Look for the dropdown** that appears below 5. **Click on the matching result** 6. **Repeat for the next scope** ![Adding Scopes Process](/img/hubspot-integration/checked-scopes.png) :::tip Almost Done Yes, it’s a few extra clicks — but it’s easy. Just check the boxes and you’ll be on your way to automatic documents in no time. Think of it like flipping switches to power things up. ⚡ ::: ### Create Your App 5. **Click "Create app"** when you're done adding scopes - Take a moment to admire your handiwork first! ![Create App Final](/img/hubspot-integration/scope-added.png) ![Create App Final](/img/hubspot-integration/app-created.png) 6. **You'll see the Access Token (needed for later)** - This is super important! - leave this page open to use this key later ![Access Token](/img/hubspot-integration/click-on-auth.png) ![Access Token](/img/hubspot-integration/access-token-copiying.png) :::warning Handle With Care This token is kind of like a temporary library card — it lets TurboDocx read only the data you’ve approved. But just like a password, you don’t want it ending up in the wrong hands. Keep it private, and if it ever gets shared by accident, no worries — you can always generate a fresh one right here. ::: ## Step 2: Configuring TurboDocx Now we'll connect your shiny new HubSpot app to TurboDocx. This is like introducing two friends who are perfect for each other! ### Navigate to TurboDocx Settings 1. **Go to your TurboDocx dashboard** - Log in if you haven't already ![TurboDocx Dashboard](/img/hubspot-integration/settings-link.png) 2. **Click on "Settings"** - Look for the gear icon or "Settings" text - Usually in the top menu or sidebar ![TurboDocx Dashboard](/img/hubspot-integration/settings-link.png) 3. **Click on "Organization Settings"** - This might be in a dropdown or separate tab - If you can't find it, try looking for "Integrations" or "Connected Apps" ![Organization Settings](/img/hubspot-integration/org-settings.png) ### Configure HubSpot Integration 4. **Find the HubSpot section** - Look for the HubSpot logo or "HubSpot Integration" - It might be in a list with other integrations ![HubSpot Section](/img/hubspot-integration/select-configure-hubspot.png) 5. **Click "Configure HubSpot"** - A popup or form will appear - This is where the magic happens! ✨ ![Configure HubSpot Button](/img/hubspot-integration/hubspot-config-modal.png) 6. **Enter your Access Token** - Copy the Access Token from your HubSpot private app page and paste it here ![Access Token](/img/hubspot-integration/access-token-copiying.png) ![Access Token](/img/hubspot-integration/hubspot-access-token-copied.png) 7. **Click "Save Configuration"** - Cross your fingers (optional, but recommended!) ![Access Token](/img/hubspot-integration/click-save-configuration.png) ### Sync Your HubSpot Data 9. **Click "Refresh Fields"** - This button appears after the connection test succeeds - It downloads all your custom HubSpot fields and data - It also tests if your access token works ![Access Token](/img/hubspot-integration/refresh-fields.png) 10. **Wait for the field sync** - This can take 1-3 minutes - Time to check your email or practice your victory dance! 💃 :::tip Success Celebration If you've made it this far, you deserve a pat on the back! You've successfully connected HubSpot to TurboDocx. That's no small feat — you're basically a tech wizard now! 🧙‍♂️ ::: ## Step 3: Using Your HubSpot Integration Time to put your new integration to work! This is where the magic happens — turning your HubSpot data into beautiful documents. ### Creating Your First Document 1. **Go to document generation** - Look for "Create Document", "New Document", or similar - This is usually on your main dashboard ![Document Generation](/img/hubspot-integration/home-page.png) 2. **Select Template** - Click on the template you want to work with ![Change Source](/img/hubspot-integration/template-page.png) 3. **Change your data source** - Click on the "Change Source" dropdown - Select "Change Resource" from the menu ![Change Source](/img/hubspot-integration/change-resource.png) 4. **Go to the App Library** - Click on the "App Library" tab - This shows all your connected integrations ![App Library](/img/hubspot-integration/app-library.png) 5. **Select CRM category** - Look for "CRM" and click on it - This filters to show only CRM integrations ![Select CRM](/img/hubspot-integration/crm-select.png) 6. **Choose HubSpot** - Click on "HubSpot" (you should see the orange logo) - It should show as "Connected" ![Choose HubSpot](/img/hubspot-integration/select-hubspot.png) 7. **Click "Continue"** - This takes you to the HubSpot agent interface ![Continue Button](/img/hubspot-integration/continue-click.png) ### Using the HubSpot Agent 8. **Select your records** (optional but helpful) - Click "Select Records" to choose specific contacts, deals, or companies - This helps the AI focus on the right data ![Select Records](/img/hubspot-integration/select-resource-btn.png) 9. **Choose relevant records** - Click on contacts, deals, or companies relevant to your document - Selected items will be highlighted - Click "Save" to confirm your record selection ![Choose Records](/img/hubspot-integration/select-records.png) 11. **Give instructions to the AI** - Type what kind of document you want in plain English - Be specific about what you want to create - Sit back and watch the magic happen! - Generation typically takes 30 seconds to 2 minutes ![AI Instructions](/img/hubspot-integration/prompt-continue.png) **Example prompts:** - "Create a professional proposal section for the selected deal using the contact's information" - "Generate a follow-up slide summarizing our recent meeting with this contact" - "Create a company overview report using the selected company data" - "Draft a project kickoff overview for the selected deal and contacts" 13. **Review your document** - Check that all the information looks correct - Make any necessary edits - Marvel at your personalized, professional document! :::tip Pro Document Tips - Be specific in your instructions — "Create a proposal" vs. "Create a detailed Q3 marketing proposal section for ABC Corp with pricing and timeline" - Select the right records — more relevant data = better documents - Don't be afraid to regenerate if the first attempt isn't perfect - Save successful prompts for future use! ::: ## Troubleshooting Even the best-laid plans sometimes go awry. Here are solutions to common issues: ### "I Can't Find the Settings Gear in HubSpot" **Solution**: - Look in the top right corner of your HubSpot dashboard - It's usually next to your profile picture - If you still can't find it, try refreshing the page or logging out and back in ### "Invalid Access Token" Error **Solution**: - Double-check that you copied the entire token (it's usually quite long) - Make sure there are no extra spaces at the beginning or end - Verify your HubSpot private app is still active - If all else fails, create a new private app and get a fresh token ### "Permission Denied" Error **Solution**: - Check that you added all the required scopes to your HubSpot private app - Make sure you're an admin in your HubSpot account - Verify the private app is enabled and not deactivated ### "No Records Found" in TurboDocx **Solution**: - Make sure you have actual data in your HubSpot account (contacts, deals, companies) - Click "Refresh Fields" again in your organization settings - Check that your HubSpot private app has the right permissions ### "The Agent Doesn't Understand My Instructions" **Solution**: - Be more specific in your prompts - Use simpler language - Include the type of document you want (email, proposal, report, etc.) - Try selecting more specific records :::tip When All Else Fails If you're still stuck, don't panic! Take a screenshot of any error messages, note exactly what step you're on, and contact your support team. We're here to help, not judge your tech skills! 🤝 ::: ## Security and Privacy Your data security is important to us (and should be to you too!): ### How Your Data is Protected - **Secure Authentication**: We use OAuth 2.0 (fancy industry-standard security) - **Limited Permissions**: TurboDocx only gets permission to read your data, not change it - **Encrypted Transmission**: All data transfers are encrypted (like sending a letter in a locked box) ### Best Practices - **Keep Your Token Secret**: Don't share your access token with anyone - **Regular Reviews**: Periodically check which integrations have access to your data - **Rotate Tokens**: Periodically rotate your tokens for maximal security posture. ## Tips for Success ### Getting the Best Results **Keep Your HubSpot Data Clean**: - Use consistent naming conventions - Fill in important fields (contact info, deal values, etc.) - Keep your data up-to-date **Write Clear Instructions**: - Be specific about what you want - Mention the type of document - Include any special requirements **Select the Right Records**: - Choose records that are relevant to your document - Don't select too many records at once - Quality over quantity! ### Advanced Tips **For Better Proposals**: - Select the contact, deal, and company records - Include deal value and timeline information - Mention specific services or products **For Better Reports**: - Select relevant companies and contacts - Include date ranges if applicable - Specify the type of analysis you want **For Better Follow-ups**: - Select recent meeting attendees - Include deal or project context - Mention next steps or action items ## What's Next? Congratulations! You've successfully: - ✅ Created a HubSpot private app (you're basically a developer now!) - ✅ Connected it to TurboDocx (networking skills: unlocked!) - ✅ Generated your first document (content creation: mastered!) ### Now You Can: - Create personalized proposals in minutes instead of hours - Build comprehensive reports using your CRM information - Automate document creation for your entire team ### Next Steps: 1. **Train your team** on being a prompting pro 2. **Create document and presentation templates** for common use cases 3. **Experiment with different AI prompts** to find what works best :::tip Final Words of Wisdom Remember, you're not just creating documents or presentations— you're creating more time for yourself by automating repetitive tasks. Every minute saved on copy-pasting customer data is a minute you can spend on more important things (like actually talking to customers!). 🎉 ::: ## Getting Help **If you need assistance:** 1. **Check this guide first** (you'd be surprised how often the answer is right here!) 2. **Take screenshots** of any error messages 3. **Note the exact step** where you got stuck 4. **Contact your TurboDocx support team** with the details **Remember**: There's no such thing as a bad question. We've all been there, and we're here to help you succeed! 💪 --- *Happy document generating! May your proposals be persuasive, your reports be comprehensive, and your follow-ups be timely! 🚀* --- # OneDrive and SharePoint Integration The OneDrive and SharePoint integration in TurboDocx offers a streamlined and effective way to manage your document generation process. With this integration, you can import templates from OneDrive or SharePoint and export deliverables back to these platforms, enhancing your workflow and collaboration efficiency. ## Configuration ### Configuring SharePoint and OneDrive for Business with TurboDocx This guide will provide you step-by-step instructions on creating a SharePoint integration that can connect to your SharePoint sites as well as OneDrive. We will be using the Microsoft Graph API to access the SharePoint and OneDrive resources. Pre-requisites: - An Office365 Tenant with SharePoint and OneDrive for Business Enabled - Access to the Admin/Azure portal and the ability to register an application. ### Step 1: Register your application in Entra ID (Azure Active Directory): 1. Sign in to the SharePoint Dashboard (or Azure Admin Portal) and click on "Entra ID (Azure Active Directory)" in the respective menu. Click on the "App Registrations and then click on "New registration" ![Entra ID App Registrations page with New Registration button highlighted](/img/sharepoint_and_onedrive/app-reg.png) 2. Enter a name for the application such as "TurboDocx Sharepoint integration", select the appropriate account type, and select Single Page Application with the redirect uri: "https://app.turbodocx.com" ![Register an application form with name, account type, and redirect URI fields](/img/sharepoint_and_onedrive/Register_Application.png) 3. Click on the "Register Button" to create the application. 4. Note down the Application (Client) ID and the Directory (tenant) ID, as you will need them later. ![Alt text](/img/sharepoint_and_onedrive/Get_Site_ID_and_Client_ID.png) ### Step 2: Configure API Permissions: 1. In the "App registrations" menu, click on "API" permissions" in the left-hand menu. 2. Click on "Add a permission" 3. Click "Microsoft Graph" and then "Delegated permissions" ![Alt text](/img/sharepoint_and_onedrive/Click_Delegated_Permissions.png) 4. Search for the following permissions and add them: **Sites.Read.All** - This permission allows TurboDocx to read all site collections, sites, lists, and list items in SharePoint on behalf of the signed-in user. :::note TurboDocx does not make API calls to your SharePoint site without user interaction. This will mirror whatever permissions the user has in the SharePoint site. ::: **Files.ReadWrite.All** - This permission allows TurboDocx to read and write (create, edit, and delete) all files in OneDrive for Business, SharePoint document libraries, and Microsoft Teams files on behalf of the signed-in user. With this permission, TurboDocx can access and manage files stored in OneDrive and SharePoint, including creating new files, updating existing files, moving or deleting files, and even sharing files with others. :::note TurboDocx does not make API calls to your SharePoint site without user interaction. Keep in mind that these are delegated permissions, which means TurboDocx can access these resources on behalf of the signed-in user. The level of access TurboDocx will have depends on the user's actual permissions in SharePoint and OneDrive. ::: ![Alt text](/img/sharepoint_and_onedrive/Graph_Permissions.png) 5. After clicking save, navigate back to "Add Permission" and click SharePoint. Click "SharePoint" and then "Delegated Permissions" ![Alt text](/img/sharepoint_and_onedrive/SharePoint_Delegated_Permissions.png) 6. Search for the following permissions and add them: **AllSites.Read** - This permission allows TurboDocx to read all site collections, sites, lists, and list items in SharePoint on behalf of the signed-in user. With this permission, TurboDocx can access and retrieve information about the SharePoint site content, such as document libraries, lists, and list items. **AllSites.Write** - This permission allows TurboDocx to write to all site collections, sites, lists, and list items in SharePoint on behalf of the signed-in user. With this permission, TurboDocx can create, edit, and delete SharePoint site content. This allows TurboDocx to save Deliverables to your SharePoint site. **MyFiles.Read** - This permission allows TurboDocx to read the signed-in user's files in OneDrive for Business. With this permission, TurboDocx can access and retrieve information about the user's files, such as file names, file types, and file metadata. **MyFiles.Write** - This permission allows TurboDocx to write to the signed-in user's files in OneDrive for Business. With this permission, TurboDocx can create, edit, and delete the user's files :::note TurboDocx does not make API calls to your SharePoint site without user interaction. This user's access will mirror whatever permissions the user has in the SharePoint site. Keep in mind that these are delegated permissions, which means TurboDocx can access these resources on behalf of the signed-in user. The level of access TurboDocx will have depends on the user's actual permissions in SharePoint and OneDrive. ::: ![Alt text](/img/sharepoint_and_onedrive/SharePoint_Permissions_Select.png) 7. The finished permission sets should look like the below: ![Alt text](/img/sharepoint_and_onedrive/Finished_Permission_Set.png) ### Step 3: Determine the default site that TurboDocx will open and get the path 1. The easiest way to do this is to navigate to your Site and find the path you want users to see first. 2. Take note of the URL path and notate it for future use. In this case it is "DocumentationTeam" ![Alt text](/img/sharepoint_and_onedrive/GetSiteName.png) ### Step 4: Login to your TurboDocx Tenant and go to Tenant Settings 1. As an admin within your TurboDocx tenant, navigate to the settings tab on the left-hand side-nav and click "Tenant Settings" in the top right corner. 2. If Hide SharePoint in the UI is selected, unselect this to get the SharePoint configuration button. 3. Click "Configure SharePoint" ![Alt text](/img/sharepoint_and_onedrive/Configure_Sharepoint_button.png) 4. Fill out the following fields with the information you have noted from previous steps and click "Save": **Tenant Name** - This is the name of the SharePoint tenant are connecting to. This can be found by looking in the browser bar and it should resemble "TenantName.SharePoint.com" **Site Name** - This is the name of the default site your users will land on when first opening the SharePoint file picker. This is noted from the previous step. **Client ID** - This is the Application (client) ID noted from the previous step in the EntraID (AzureAD) console. **Tenant ID** - This is the Directory (tenant) ID noted from the previous step in the EntraID (AzureAD) console. ![Alt text](/img/sharepoint_and_onedrive/SharePoint_Configuration_in_TurboDocx.png) 5. Test importing and Exporting templates using the steps noted in the following sections. ## Importing Templates One of the primary benefits of the SharePoint integration in TurboDocx is the ability to directly import templates from your SharePoint document libraries. This feature removes the need to manually upload templates to TurboDocx, streamlining the process and saving you both time and effort. By simply connecting your SharePoint tenant to TurboDocx, you can effortlessly access and import your templates. The integration offers a seamless browsing experience, enabling you to navigate through your SharePoint document libraries and folders to choose the desired templates for import. Whether you have pre-existing templates or wish to import templates created using other applications such as Microsoft Word or Office 365, the SharePoint integration simplifies the process. Upon selecting the templates, TurboDocx securely imports them into your TurboDocx account, ready for customization and document generation. This ensures that your templates are readily available within TurboDocx, eliminating the need for manual file transfers or duplicate storage. ![Alt text](/img/sharepoint_and_onedrive/Import_with_SharePoint.png) ## Exporting Deliverables The SharePoint integration also enables you to export deliverables generated by TurboDocx back to your SharePoint and One Drive for business accounts. Once you have personalized and generated documents based on your templates, you can seamlessly export them to your SharePoint storage for easy access and sharing. Exporting deliverables to SharePoint and One Drive for Business ensures that your documents are stored securely in the cloud, providing a reliable backup and making them accessible from any device with an internet connection. By leveraging the familiar SharePoint interface, you can organize your deliverables into folders, share them with collaborators, and control access permissions as needed. --- ## Salesforce Integration # Turn Your Salesforce Data into Professional Documents & Presentations Say goodbye to copy-pasting customer information! TurboDocx's Salesforce integration automatically pulls your real CRM data to create personalized documents, proposals, and presentations. No more "John Doe" placeholder text — use actual account names, opportunities, and details. ## What You Can Create - **📊 Sales Proposals**: Use real opportunity data to create compelling, personalized proposals - **📄 Account Reports**: Generate comprehensive reports with actual customer information - **📋 Meeting Summaries**: Create professional meeting notes using your Salesforce data - **💼 Sales Presentations**: Build custom presentations with live customer data - **📝 Follow-up Documents**: Generate personalized follow-ups using contact details - **🔄 Automated Reports**: Create recurring reports with fresh data from Salesforce ## Before You Begin :::tip For Our Technology-Shy Friends Don't worry! We've made this guide so detailed that any team member can follow it. Think of it as cooking instructions, but for software — and just as tasty! 😄 ::: To use the Salesforce integration, you'll need: - A Salesforce account with Connected App creation permissions (any edition works!) - System Administrator access to create a Salesforce connected app - About 15 minutes and a cup of coffee ☕ - This guide (which you're already reading — you're ahead of the game!) :::tip Quick note This process involves creating something called a "connected app" in Salesforce. Think of it as giving TurboDocx secure access to your Salesforce data — and only the data you want to share. ::: ## Step 1: Create a Private Salesforce External Client App Time to get VIP access to your Salesforce data! 🎭 This step guides you through creating a new external client app in Salesforce, which will serve as the secure bridge for your TurboDocx application. Think of it as getting backstage passes to your very own Salesforce concert! 🎫 ### Log in to Salesforce 1. **Open your web browser** and go to [salesforce.com](https://salesforce.com) - Use Chrome, Firefox, Safari, or Edge 2. **Log into your Salesforce account** - Use your normal email and password with appropriate administrative privileges - If you forgot your password, there's a "Forgot Password?" link (we've all been there!) ![Salesforce Login Screen](/img/salesforce-integration/Login_Window.png) ### Navigate to Setup Home 3. **Find the Setup gear** in the top right corner - It looks like a little wheel with teeth ⚙️ - It's located next to your profile picture icon on the home page ![Setup Icon and Menu](/img/salesforce-integration/Setup_Icon_and_Menu.png) 4. **Click on "Setup"** - Select "Setup" from the dropdown menu - You will be navigated to the Setup Home page ![Salesforce Setup Home Page](/img/salesforce-integration/Setup_Home_Page.png) ### Go to App Manager 5. **Navigate through the left sidebar menu** - From the Setup Home page, scroll down in the left sidebar - Navigate to: **Platform Tools > Apps > App Manager** ![Navigation to App Manager in Sidebar](/img/salesforce-integration/Navigation_to_App_Manager.png) ### Create a New External Client App 6. **Click "New External Client App"** - On the App Manager screen, look for the "New External Client App" button - Click this button (not "New Lightning App") ![App Manager Screen with New External Client App Button](/img/salesforce-integration/New_External_Client_App_Button.png) ### Configure Basic Information 7. **Fill in the Basic Information section**: - **External Client App Name**: Type "TurboDocx Integration App" or "TurboDocx Document Generator" - **API Name**: This will auto-populate based on the App Name, but you can adjust if needed - **Contact Email**: Enter a relevant contact email address - **Distribution State**: Set this to "Local" ![Basic Information Section Fields](/img/salesforce-integration/Basic_Information.png) :::tip Pro Tip Choose an app name you'll remember six months from now. "App123" might seem clever today, but future you will not be amused! 😏 ::: ### Configure API Settings 8. **Set up OAuth Settings** - Proceed to the "API Settings" section - **Enable OAuth Settings**: Check this box - **Callback URL**: Enter `https://api.turbodocx.com/oauth/salesforce/callback` as the callback URL ![API Settings with OAuth Enabled](/img/salesforce-integration/OAuth_Enabled.png) 9. **Select OAuth Scopes** - In the **Selected OAuth Scopes** section, select the following scopes: - `Access unique user identifiers (openid)` - `Manage user data via APIs (api)` - `Manage user data via Web browsers (web)` - `Perform requests at any time (refresh_token, offline access)` ![OAuth Scopes Selected](/img/salesforce-integration/OAuth_Scopes_Selected.png) :::tip Attention to Detail Required You'll be adding several different permissions. It's like checking off a grocery list — but necessary for the feast ahead! 🛒 ::: 10. **Configure Flow Enablement** - In the "Flow Enablement" section under OAuth settings, make sure only the following checkbox is ticked - **Enable Authorization Code and Credentials Flow** ![Flow and Enablement Settings](/img/salesforce-integration/Flow_Enablement_settings.png) 11. **Configure Security** - In the "Security" section under OAuth settings, make sure only the following checkbox is ticked: - **Issue JSON Web Token (JWT)-based access tokens for named users** ![Security Settings](/img/salesforce-integration/Security_Section.png) 12. **Create the External Client App** - Click "Create" to create your external client app - Take a moment to admire your handiwork first! ![Save External Client App](/img/salesforce-integration/create_app.png) :::tip Achievement Unlocked: App Creator! 🏆 Look at you go! You've successfully created your Salesforce External Client App. You're basically a mini developer now! 👩‍💻 Now we need to configure its policies and get those important credentials. ⚡ ::: ## Step 2: Configure Connected App Policies and Retrieve Credentials After creating the app, you need to adjust its access policies and retrieve the necessary credentials for TurboDocx. ### Access App Details Page 1. **Navigate to your app's detail page** - You should be automatically redirected to the detail page of the app you just created as soon as you hit the create button - If not, navigate to External Client App Manager **(Platform Tools > Apps > External Client App > External Client App Manager)** and click on the name of your newly created app ![Connected App Detail Page](/img/salesforce-integration/Connected_app_details_page.png) ### Edit Policies 2. **Locate the Policies section** - On the app's detail page, find the "Policies" section - Click the "Edit" button located below the policies section ![Policies Section with Edit Button](/img/salesforce-integration/policies_section.png) 3. **Adjust OAuth Policies** - In the "Edit" view, locate the OAuth policies and configure the following settings: - **Permitted Users**: Select **"All users may self-authorize"** - **Named User JWT-Based Access Token Settings**: Select **30 minutes** for token timeout - **Refresh Token Policy**: Select **"Refresh token is valid until revoked"** - **IP Relaxation**: Select **"Relax IP restrictions"** ![OAuth Policies Configuration](/img/salesforce-integration/OAuth_policies.png) 4. **Save Changes** - Click "Save" to apply the policy changes ![Save Button](/img/salesforce-integration/Save_policies.png) ### Retrieve Consumer Key and Secret 5. **Find Settings section** - On the same app details page, Navigate to the "Settings" section - Here you don't need to edit anything as we have already configured it while creating the app ![Settings Section](/img/salesforce-integration/connected_app_settings_section.png) 6. **Navigate to the OAuth Settings part under the settings section** - You will now see a button of **Consumer Key and Secret** - Click on this button ![Consumer Key and Secret Button](/img/salesforce-integration/Consumer_key_and_secret_button.png) 7. **Complete identity verification** - You will be prompted to verify your identity via an OTP being sent to your registered email - Complete this verification step ![Identity Verification Prompt](/img/salesforce-integration/Identity_Verification_page.png) 8. **Copy your credentials** - After successful verification, your Consumer Key and Consumer Secret will be displayed - Copy both the **Consumer Key** and **Consumer Secret** - these credentials are essential for connecting your TurboDocx application to Salesforce ![Consumer Key and Secret Display](/img/salesforce-integration/Consumer_key_and_secret_page.png) :::warning Handle With Care 🔐 These consumer keys are like VIP backstage passes to your Salesforce show — they let TurboDocx read only the data you've approved, but you definitely don't want random people crashing your party! 🎉 Keep them private, and if they ever get shared by accident, no worries — you can always generate fresh ones right here. It's like getting a new set of keys! 🔑 ::: ## Step 3: Configuring TurboDocx Now we'll connect your shiny new Salesforce external client app to TurboDocx. This is like introducing two friends who are perfect for each other at a party — and watching the magic happen! 🎉✨ ### Navigate to TurboDocx Settings 1. **Go to your TurboDocx dashboard** 📊 - Log in if you haven't already (we'll wait!) ![TurboDocx Main Dashboard](/img/salesforce-integration/Turbodocx_dashboard.png) 2. **Click on "Settings"** - Look for the gear icon or "Settings" text - Usually in the top menu or sidebar ![Settings Menu](/img/salesforce-integration/turbodocx_settings_page.png) 3. **Click on "Organization Settings"** - This might be in a dropdown or separate tab - If you can't find it, try looking for "Integrations" or "Connected Apps" ![Organization Settings Page](/img/salesforce-integration/Turbodocx_org_page.png) ### Configure Salesforce Integration 4. **Find the Salesforce section** - Look for the Salesforce logo or "Salesforce Integration" - It might be in a list with other integrations ![Salesforce Integration Section](/img/salesforce-integration/Salesforce_integration_page.png) 5. **Click "Configure Salesforce"** 🚀 - A popup or form will appear - This is where the magic happens! Time to make these two apps best friends! ✨👯‍♀️ ![Configuration Button](/img/salesforce-integration/configure_salesforce_button.png) ![Configuration Modal](/img/salesforce-integration/configuration_modal.png) 6. **Enter your Consumer Key and Consumer Secret** - Copy the Consumer Key from your Salesforce connected app page and paste it here - Copy the Consumer Secret from your Salesforce connected app page and paste it in the Consumer Secret field ![Key Entry Form](/img/salesforce-integration/key_and_secret_entered.png) 7. **Click "Save Configuration"** - Cross your fingers (optional, but recommended!) ![Save Configuration Button](/img/salesforce-integration/Save_config_button.png) ### Establish the OAuth Flow for Salesforce 8. **Click "Connect to Salesforce"** - This button appears after you save your configuration - You'll be redirected to Salesforce to authorize the connection ![Connection Button](/img/salesforce-integration/connect_to_salesforce_button.png) 9. **Authorize TurboDocx in Salesforce** - Salesforce will ask you to log in and confirm the connection - Enter your username and password to login ### Sync Your Salesforce Data 10. **Click "Refresh Fields"** - This button appears after the connection test succeeds - It downloads all your custom Salesforce fields and data - It also tests if your connection works ![Field Refresh Button](/img/salesforce-integration/refresh_fields.png) 11. **Wait for the field sync** ⏳ - This can take 2-5 minutes depending on your Salesforce org size - Perfect time to check your email, grab a snack, or practice your victory dance! 💃🍿 - Pro tip: The bigger your Salesforce org, the more impressive your setup skills! 💪 :::tip Success Celebration 🎊 If you've made it this far, you deserve a pat on the back! You've successfully connected Salesforce to TurboDocx. That's no small feat — you're basically a tech wizard now! 🧙‍♂️ Time for a victory dance! 💃🕺 ::: ## Step 4: Using Your Salesforce Integration Time to put your new integration to work! This is where the real magic happens — turning your boring Salesforce data into beautiful, professional documents that'll make your colleagues go "WOW!" 🤩✨ ### Creating Your First Document 1. **Go to document generation** - Look for "Create Document", "New Document", or similar - This is usually on your main dashboard ![Document Creation Page](/img/salesforce-integration/Create_document_button.png) 2. **Select Template** - Click on the template you want to work with ![Template Selection](/img/salesforce-integration/template_selection_page.png) 3. **Change your data source** - Click on the "Change Source" dropdown - Select "Change Resource" from the menu ![Source Change Dropdown](/img/salesforce-integration/source_change_dropdown.png) 4. **Go to the App Library** - Click on the "App Library" tab - This shows all your connected integrations ![App Library Interface](/img/salesforce-integration/app_library_interface.png) 5. **Select CRM category** - Look for "CRM" and click on it - This filters to show only CRM integrations ![CRM Category Selection](/img/salesforce-integration/CRM_category_selection.png) 6. **Choose Salesforce** - Click on "Salesforce" (you should see the Salesforce logo) - It should show as "Connected" ![Salesforce Selection](/img/salesforce-integration/Salesforce_selection.png) 7. **Click "Continue"** - This takes you to the Salesforce agent interface ![Continue Button](/img/salesforce-integration/salesforce_continue_button.png) ### Using the Salesforce Agent 8. **Select your records** (optional but helpful) - Click "Select Records" to choose specific accounts, opportunities, contacts and many other fields - This helps the AI focus on the right data ![Record Selection Interface](/img/salesforce-integration/record_selection_interface.png) 9. **Choose relevant records** - Click on accounts, opportunities, contacts or other fields relevant to your document - Selected items will be highlighted - Click "Save" to confirm your record selection ![Record Selection Process](/img/salesforce-integration/record_selection_process.png) 10. **Give instructions to the AI** 🤖 - Type what kind of document you want in plain English (no tech jargon needed!) - Be specific about what you want to create - Sit back, relax, and watch the magic happen! ✨ - Generation typically takes 30 seconds to 2 minutes (perfect time for a coffee sip! ☕) ![Prompt Input Interface](/img/salesforce-integration/prompt_input_interface.png) **Example prompts:** - "Create a professional proposal section for the selected opportunity using the account's information" - "Generate a follow-up slide summarizing our recent meeting with this account" - "Create a company overview report using the selected account data" - "Draft a project kickoff overview for the selected opportunity and contacts" 11. **Review your document** 👀 - Check that all the information looks correct - Make any necessary edits - Marvel at your personalized, professional document and do a little happy dance! 💃🕺 :::tip Pro Document Tips - Be specific in your instructions — "Create a proposal" vs. "Create a detailed Q3 marketing proposal section for ABC Corp with pricing and timeline" - Select the right records — more relevant data = better documents - Don't be afraid to regenerate if the first attempt isn't perfect - Save successful prompts for future use! ::: ## Troubleshooting 🤔 Even the best-laid plans sometimes go awry (Murphy's Law is real, folks!). Don't worry — we've got your back! Here are solutions to common issues: ### "I Can't Find the Setup Menu in Salesforce" **Solution**: - Look in the top right corner of your Salesforce interface - Click on your profile picture or the gear icon - Select "Setup" from the dropdown menu - If you still can't find it, you might need System Administrator permissions ### "I Can't Find 'New External Client App'" **Solution**: - Make sure you're in App Manager (Platform Tools > Apps > App Manager) - Look for "New External Client App" button, not "New Lightning App" - If you don't see this option, you might need System Administrator permissions - Some Salesforce orgs might have different permission requirements ### "Invalid Consumer Key/Secret" Error **Solution**: - Double-check that you copied the entire key and secret (they're usually quite long) - Make sure there are no extra spaces at the beginning or end - Verify your Salesforce external client app is still active - Complete the identity verification step when accessing "Manage Consumer Details" - If all else fails, create a new external client app and get fresh keys ### "Permission Denied" Error **Solution**: - Check that you added all the required OAuth scopes to your Salesforce external client app - Verify you selected: OpenID, API, Web, and Refresh token scopes - Make sure you're a System Administrator in your Salesforce org - Verify the external client app is enabled and policies are configured correctly ### "No Records Found" in TurboDocx **Solution**: - Make sure you have actual data in your Salesforce org (accounts, opportunities, contacts) - Click "Refresh Fields" again in your organization settings - Check that your Salesforce external client app has the right permissions and policies configured ### "The Agent Doesn't Understand My Instructions" **Solution**: - Be more specific in your prompts - Use simpler language - Include the type of document you want (email, proposal, report, etc.) - Try selecting more specific records :::tip When All Else Fails 🎆 If you're still stuck, don't panic! Take a deep breath — you've got this! 💪 Take a screenshot of any error messages, note exactly what step you're on, and contact our support team. We're here to help, not judge your tech skills (we've all been there!). Think of us as your friendly tech support sidekicks! 🤝🦾 ::: ## Security and Privacy Your data security is important to us (and should be to you too!): ### How Your Data is Protected - **Secure Authentication**: We use OAuth 2.0 (fancy industry-standard security) - **Limited Permissions**: TurboDocx only gets permission to read your data, not change it - **Encrypted Transmission**: All data transfers are encrypted (like sending a letter in a locked box) ### Best Practices - **Keep Your Keys Secret**: Don't share your consumer key and secret with anyone - **Regular Reviews**: Periodically check which integrations have access to your data - **Monitor Connected Apps**: Regularly review connected apps in your Salesforce org ## Tips for Success ### Getting the Best Results **Keep Your Salesforce Data Clean**: - Use consistent naming conventions - Fill in important fields (account info, opportunity values, etc.) - Keep your data up-to-date **Write Clear Instructions**: - Be specific about what you want - Mention the type of document - Include any special requirements **Select the Right Records**: - Choose records that are relevant to your document - Don't select too many records at once - Quality over quantity! ### Advanced Tips **For Better Proposals**: - Select the account, opportunity, and contact records - Include opportunity value and close date information - Mention specific products or services **For Better Reports**: - Select relevant accounts and opportunities - Include date ranges if applicable - Specify the type of analysis you want **For Better Follow-ups**: - Select recent meeting attendees - Include opportunity or project context - Mention next steps or action items ## What's Next? 🎆 Congratulations, integration superstar! You've successfully: - ✅ Created a Salesforce external client app (you're basically a developer now! 👩‍💻) - ✅ Configured its policies and retrieved credentials (security expert: level unlocked! 🔒) - ✅ Connected it to TurboDocx (networking ninja: mastered! 🥷) - ✅ Generated your first document (content creation wizard: achieved! 🧙‍♀️) ### Now You Can: 🎉 - Create personalized proposals in minutes instead of hours (time saver level: EXPERT! ⏱️) - Build comprehensive reports using your CRM information (data wizard status: UNLOCKED! 📊) - Automate document creation for your entire team (team hero achievement: EARNED! 🦾) ### Next Steps: 🚀 1. **Train your team** on being a prompting pro (share your newfound expertise!) 2. **Create document and presentation templates** for common use cases (you're basically a template architect now!) 3. **Experiment with different AI prompts** to find what works best (become the AI whisperer of your office! 🤖🗣️) :::tip Final Words of Wisdom 🎓 Remember, you're not just creating documents or presentations — you're creating more time for yourself by automating repetitive tasks. Every minute saved on copy-pasting customer data is a minute you can spend on more important things (like actually talking to customers, or grabbing that extra cup of coffee! ☕). You're officially a productivity ninja now! 🎉🥷 ::: ## Getting Help **If you need assistance:** 1. **Check this guide first** (you'd be surprised how often the answer is right here!) 2. **Take screenshots** of any error messages 3. **Note the exact step** where you got stuck 4. **Contact our TurboDocx support team** with the details **Remember**: There's no such thing as a bad question. We've all been there, and we're here to help you succeed! You're part of the TurboDocx family now! 💪🏠 --- _Happy document generating, integration champion! May your proposals be persuasive, your reports be comprehensive, your follow-ups be timely, and your coffee always be hot! ☕🚀✨_ --- # Microsoft Teams Integration 🚀 Coming Soon Transform your Microsoft Teams meetings into professional documents and presentations with AI-powered automation. ## What's Coming We're actively developing a comprehensive Microsoft Teams integration that will revolutionize how you handle meeting documentation and follow-ups. Here's what you can expect: ### 🎯 Planned Features 📄 Meeting Documentation Convert Teams meeting transcripts into formatted documents Generate meeting minutes with action items Create follow-up reports automatically Extract key decisions and takeaways 📊 Presentation Generation Turn discovery calls into client presentations Create pitch decks from meeting discussions Generate training materials from sessions Build project proposals from requirement gatherings 🤖 AI-Powered Automation Intelligent content extraction from transcripts Smart formatting and structuring Context-aware document generation Automated workflow triggers 🔄 Seamless Integration Direct access to Teams cloud recordings OAuth-based secure authentication Real-time transcript processing Multi-tenant support ### 🎨 Document Types You'll Be Able to Create **📋 Business Documents** - Meeting summaries and action items - Project status reports - Client consultation notes - Team retrospective documents **📈 Sales & Marketing Materials** - Customer discovery presentations - Sales proposal documents - Product demo follow-ups - Marketing campaign briefs **🎓 Training & Education** - Workshop documentation - Training session summaries - Educational content creation - Knowledge base articles **💼 HR & Operations** - Interview documentation - Team meeting notes - Process documentation - Compliance reports ## Why Teams Integration Matters ⚡ Accelerate Post-Meeting Workflows Eliminate manual note-taking and document creation after every Teams meeting 🎯 Improve Meeting ROI Transform every Teams meeting into actionable, professional deliverables 🤝 Enhance Team Collaboration Create consistent documentation standards across all Teams meetings 📊 Leverage Your Microsoft Investment Maximize the value of your Teams platform with intelligent document automation ## Stay Updated ### 📬 Get Notified When Teams Integration Launches Want to be among the first to know when Microsoft Teams integration becomes available? We'll keep you updated on our progress and let you know as soon as it's ready for use. **Contact our team to:** - Get early access notifications - Provide input on feature priorities - Schedule a preview demonstration - Discuss your specific Teams integration needs 📧 Email: team@turbodocx.com 💬 Subject: "Teams Integration Updates" ## Technical Preview ### 🔬 Beta Testing Program We're looking for organizations to participate in our Teams integration beta testing program. Beta participants will get: - **Early access** to Teams integration features - **Direct influence** on feature development - **Priority support** during implementation - **Reduced pricing** for early adopters **Requirements for beta participation:** - Active Microsoft Teams usage with cloud recording - Willingness to provide feedback and testing Interested in beta testing? Apply for Beta Access --- 🚀 The Future of Meeting Documentation Microsoft Teams integration will transform how your organization handles meeting follow-ups, documentation, and collaboration workflows. Stay tuned for updates on availability and beta program announcements. --- # How to Setup AI Variable Configuration AI variables let you define a prompt that TurboDocx uses to generate content automatically when a document is created from a Wrike automation. Instead of pulling a static value from a Wrike field, AI interprets the project context and writes the content for you. ## Prerequisites - A template uploaded to TurboDocx with at least one variable - A connected Wrike account (see [Setting Up a Wrike Automation](./setting-up-automation.md)) ## Configure an AI Variable ### Step 1: Select Your Template Navigate to your templates and select the template you want to configure with an AI variable. ![Select Template](/img/wrike-integration/FieldMap01-SelectTemplate.jpeg) ### Step 2: Open Template Preferences Click **Edit Template & Preferences** to open the template configuration view. ![Edit Template and Preferences](/img/wrike-integration/FieldMap02-EditTemplatePreferences.jpeg) ### Step 3: Open Variable Settings Click the settings icon on the variable you want to configure with AI. ![Open Variable Settings](/img/wrike-integration/FieldMap03-OpenVariableSettings.jpeg) ### Step 4: Enable AI Click **"Use AI for variable"** to switch the variable to AI mode. This tells TurboDocx to generate content using a prompt instead of expecting manual input or a static field value. ![Use AI for variable](/img/wrike-integration/AIVar01-UseAIForVariable.jpeg) ### Step 5: Type in a Prompt Enter the prompt that AI will use to generate content for this variable. Write a clear, specific instruction describing what the AI should produce — for example, *"Write a professional project summary based on the task details and deliverables."* ![Type in a Prompt](/img/wrike-integration/AIVar02-TypePrompt.jpeg) :::tip The more specific your prompt, the better the output. Include details like tone, length, and what information to focus on. ::: ### Step 6: Save Changes Click **"Save Changes"** to persist the AI variable configuration. ![Save Changes](/img/wrike-integration/AIVar03-SaveChanges.jpeg) ## How It Works at Generation Time When a Wrike automation triggers document generation, TurboDocx sends the AI prompt along with the relevant Wrike project data to the AI model. The AI generates content based on your prompt and the project context, and the result is placed into the template variable. This is different from [static field mapping](./field-mapping.md), which inserts exact Wrike field values with no interpretation. AI variables are ideal for generating summaries, descriptions, recommendations, and other narrative content that benefits from intelligent synthesis of project data. :::tip You can mix AI variables and static field mappings in the same template. Use static mappings for structured data (dates, amounts, codes) and AI variables for narrative content (summaries, descriptions, recommendations). ::: --- # How to Setup Document Generation Automation After [setting up your Wrike automation](./setting-up-automation.md) with a trigger status and folder, follow these steps to configure it to automatically generate documents from a template. ## Configure Document Generation ### Step 1: Select Generate Document Click the **Generate Document** action card in the automation step list to select it as the action to perform when the trigger fires. ![Select Generate Document](/img/wrike-integration/Step10-SelectGenerateDocument.png) ### Step 2: Advance to Template Selection Click the **Next** button at the bottom-right of the Create Automation dialog to proceed to template selection. ![Advance to Next Step](/img/wrike-integration/Step11-AdvanceNextStep.png) ### Step 3: Browse Document Templates Click the **Browse** button in the Document Template section to open the template selection interface. ![Browse Document Templates](/img/wrike-integration/Step12-BrowseTemplates.png) ### Step 4: Choose a Template Click on the template card you want to use for automated document generation. ![Choose a Template](/img/wrike-integration/Step13-ChooseTemplate.png) ### Step 5: Confirm the Selected Template Click the **Select Template** button at the bottom-right corner of the dialog to finalize your template selection. ![Confirm Selected Template](/img/wrike-integration/Step14-ConfirmTemplate.png) ### Step 6: Proceed to Final Configuration Click the **Next** button to advance to the final configuration step. ![Proceed to Final Configuration](/img/wrike-integration/Step15-ProceedNextStep.png) ### Step 7: Name Your Automation Click inside the **NAME YOUR AUTOMATION** text input field and enter a descriptive name for your automation (e.g., "Project Delivery - Generate Proposal"). ![Name Your Automation](/img/wrike-integration/Step16-NameAutomation.png) ### Step 8: Create and Activate the Automation Click the **Create Automation** button to save and activate your automation workflow. ![Create and Activate Automation](/img/wrike-integration/Step17-CreateAutomation.png) ## Test the Integration Now that your automation is active, test it end-to-end: 1. **Open the Wrike folder** you configured in the automation 2. **Create a new task** with some project details and a description (the AI will use this data to populate your template) 3. **Change the task status** to the trigger status you configured (e.g., "Generate Document") 4. **Wait 30 seconds to 2 minutes** depending on document complexity 5. **Check the task attachments** — your generated document should appear! :::tip Testing Tips - Use descriptive task titles and descriptions — the AI uses this data to generate better documents - Fill in custom fields with relevant project information for richer output - Start with a simple template to verify the connection works before using complex ones ::: ## What's Next? - **[How to Setup Static Field Mapping](./field-mapping.md)** to template variables for static data like revenue and dates - **[How to Add Signature Anchors](./signature-anchors.md)** to your template for digital signing - **Create multiple automations** for different project types, templates, or trigger statuses - If something isn't working, see [Troubleshooting and FAQ](./troubleshooting.md) --- # How to Setup Static Field Mapping Static field mapping lets you map Wrike custom fields directly to TurboDocx template variables. When a document is generated, the exact value from the Wrike field is placed into the template variable — no AI interpretation or transformation is applied. ## Prerequisites - A template uploaded to TurboDocx with at least one variable - A connected Wrike account (see [Setting Up a Wrike Automation](./setting-up-automation.md)) ## Map a Wrike Field ### Step 1: Select Your Template Navigate to your templates and select the template you want to configure with Wrike field mappings. ![Select Template](/img/wrike-integration/FieldMap01-SelectTemplate.jpeg) ### Step 2: Open Template Preferences Click **Edit Template & Preferences** to open the template configuration view. ![Edit Template and Preferences](/img/wrike-integration/FieldMap02-EditTemplatePreferences.jpeg) ### Step 3: Open Variable Settings Click the settings icon on the variable you want to map to a Wrike field. ![Open Variable Settings](/img/wrike-integration/FieldMap03-OpenVariableSettings.jpeg) ### Step 4: Open the Field Type Menu Click the field type selector to change the variable's data source. ![Open Field Type Menu](/img/wrike-integration/FieldMap04-OpenFieldTypeMenu.jpeg) ### Step 5: Select "Wrike Field" Choose **Wrike Field** from the field type options. This tells TurboDocx to pull data from a Wrike custom field when generating the document. ![Select Wrike Field](/img/wrike-integration/FieldMap05-SelectWrikeField.jpeg) ### Step 6: Search and Select the Wrike Field Use the search box to look up the Wrike custom field you want to map (e.g., "Revenue", "Budget", or any custom field in your Wrike workspace), then click it from the results to map it to your template variable. ![Search and Select Wrike Field](/img/wrike-integration/FieldMap07-ClickField.jpeg) ### Step 7: Save Changes Click **Save Changes** to persist the field mapping. ![Save Changes](/img/wrike-integration/FieldMap08-SaveChanges.jpeg) ## How It Works at Generation Time When a Wrike automation triggers document generation, TurboDocx reads the mapped custom field values from the Wrike task and places them directly into the corresponding template variables — the value is used exactly as-is with no AI processing. This is different from AI-mapped variables, which use AI to interpret and generate content from Wrike data. Static mapping is ideal for structured data like revenue figures, dates, project codes, and other fields where you need the exact value. :::tip You can map multiple variables in the same template to different Wrike fields. Repeat steps 3–7 for each variable you want to connect. ::: --- ## Wrike Integration # Automate Document Generation from Wrike Projects TurboDocx integrates with Wrike to automatically generate professional documents, proposals, and presentations directly from your project management data. When a task status changes in Wrike, TurboDocx can automatically create and attach documents to your projects. :::tip See It in Action Want to see the full workflow before diving into setup? Check out the [End-to-End Example](./signature-workflow.md) — generate a proposal and send it for signature, all from Wrike. ::: ## What You Can Create - **Statements of Work (SOWs)**: Generate comprehensive SOWs from project folders with timelines and deliverables - **Project Proposals**: Create proposals from opportunity data in Wrike - **Status Reports**: Automatically generate project status reports from task data - **Meeting Summaries**: Turn Wrike tasks into professional meeting documentation - **Client Deliverables**: Generate client-facing documents from project information - **Executive Summaries**: Create management reports from Wrike folder data ## Before You Begin :::tip Not a Wrike Admin? This guide walks you through the entire setup process step-by-step. If you can create a task in Wrike, you can set this up! ::: You'll need: - Admin access to your Wrike workspace - Admin access to your TurboDocx organization - A template ready in TurboDocx (see [How to Create a Template](../../TurboDocx%20Templating/How%20to%20Create%20a%20Template.md)) - About 5 minutes ## How It Works The Wrike integration uses a **status-triggered automation workflow** that you configure entirely through the TurboDocx UI: 1. **Connect Wrike** to TurboDocx using your Wrike API key 2. **Create an automation** that specifies which trigger status, folder, and template to use 3. When a task status changes to your trigger status, TurboDocx automatically generates the document 4. The finished document is attached back to the Wrike task ## Guides | Guide | Description | |-------|-------------| | [End-to-End Example](./signature-workflow.md) | Watch the full workflow in action — generate a proposal and send it for signature | | [Setting Up a Wrike Automation](./setting-up-automation.md) | Connect Wrike and create an automation with a trigger status and folder | | [How to Setup Document Generation Automation](./document-generation-automation.md) | Configure an automation to generate documents from a template | | [How to Setup E-Signature Automation](./signature-automation.md) | Generate documents and send them for e-signature automatically | | [How to Setup Static Field Mapping](./field-mapping.md) | Map Wrike custom fields (revenue, dates, etc.) directly to template variables | | [How to Setup AI Variable Configuration](./ai-variable.md) | Configure AI-driven variables that generate content from prompts during automation | | [How to Add Signature Anchors](./signature-anchors.md) | Configure signature anchor fields in your template for TurboSign | | [Troubleshooting and FAQ](./troubleshooting.md) | Common issues, solutions, and frequently asked questions | ## Workflow Ideas **SOW Generation:** 1. Create a project folder in Wrike with all deliverables as tasks 2. Move the folder to "Generate SOW" status 3. TurboDocx generates a comprehensive SOW from all tasks 4. Review and send to client **Proposal Automation:** 1. Create an opportunity in Wrike with project details and pricing 2. Change status to "Generate Proposal" 3. Review the AI-generated proposal 4. Change status to "Send for Signature" for digital signing **Weekly Reporting:** 1. Create a "Weekly Report" task linked to relevant project tasks 2. Every Friday, change the status to "Generate Document" 3. Auto-generated report appears in attachments --- # Setting Up a Wrike Automation This guide walks you through connecting your Wrike account to TurboDocx and creating an automation with a trigger status and monitored folder. Once complete, you'll configure the automation's action in a follow-up guide. ## Prerequisites Before starting, make sure you have: - A **Wrike API access token** (see [Get Your Wrike Access Token](#get-your-wrike-access-token) below) - A **template** in TurboDocx ready for document generation (see [How to Create a Template](../../TurboDocx%20Templating/How%20to%20Create%20a%20Template.md)) - The **Wrike folder permalink** for the folder you want to monitor ## Get Your Wrike Access Token 1. Log into **Wrike** 2. Go to **Settings > Apps & Integrations > API** 3. Create a new **permanent access token** or use an existing one 4. Copy the token — you'll paste it in the next section For detailed instructions, see [Wrike's official API documentation](https://help.wrike.com/hc/en-us/articles/210409445-Wrike-API). ## Connect Wrike to TurboDocx ### Step 1: Open Wrike Integration Configuration Navigate to the **Integrations** page within TurboDocx. Locate the **Wrike Integration** card and click the **Configure Wrike** button to open the configuration panel. ![Open Wrike Integration Configuration](/img/wrike-integration/Step01-OpenWrikeConfig.png) ### Step 2: Enter Your Wrike API Key :::tip Need an API key? See [Wrike's API documentation](https://help.wrike.com/hc/en-us/articles/210409445-Wrike-API) for how to generate a permanent access token. ::: In the Wrike Automations connection dialog, locate the **Enter your Wrike API key** text input field. Click inside the field and paste your Wrike API access token. ![Enter Wrike API Key](/img/wrike-integration/Step02-FocusApiKeyInput.png) ### Step 3: Connect Your Wrike Account Click the **Connect Wrike** button to validate your API key and establish the connection between Wrike and TurboDocx. ![Connect Wrike Account](/img/wrike-integration/Step03-ConnectWrike.png) ## Create Your First Automation ### Step 4: Start Creating an Automation Click the **+ Create Your First Automation** button in the Wrike Automations popup to begin setting up a new automation workflow. ![Create First Automation](/img/wrike-integration/Step04-CreateFirstAutomation.png) ### Step 5: Select a Trigger Status Click on the **Select one or more statuses...** input field inside the **Trigger Status** dropdown. Select one or more workflow statuses that should trigger the automation. ![Select Trigger Status](/img/wrike-integration/Step05-SelectTriggerStatus.png) ### Step 6: Choose the Specific Trigger Status In the Trigger Status dropdown list, click the status option you want to use as the trigger (e.g., **Generate Document – Project Delivery**). ![Choose Trigger Status Option](/img/wrike-integration/Step06-ChooseTriggerOption.png) ### Step 7: Enter the Wrike Folder Permalink Click on the **Wrike Folder Permalink** text box in the Folder Location section. Paste or type the Wrike folder URL that this automation should monitor. :::tip Getting Your Folder Permalink In Wrike, right-click on the folder or project you want to monitor and select **Copy Link**. The URL will look like: `https://www.wrike.com/open.htm?id=1234567890` ::: ![Enter Folder Permalink](/img/wrike-integration/Step07-EnterFolderPermalink.png) ### Step 8: Verify the Folder Permalink Click the **Verify Permalink** button to validate that the provided Wrike folder URL is correct and accessible. ![Verify Folder Permalink](/img/wrike-integration/Step08-VerifyPermalink.png) ### Step 9: Proceed to the Next Step Click the **Next** button at the bottom-right corner of the setup modal to continue to action configuration. ![Proceed to Next Step](/img/wrike-integration/Step09-ClickNext.png) ## What's Next? After completing the base automation setup above, choose which type of automation to configure: - **[How to Setup Document Generation Automation](./document-generation-automation.md)** — automatically generate documents from a template when the trigger fires - **[How to Setup E-Signature Automation](./signature-automation.md)** — generate documents and send them for digital signature via TurboSign --- # How to Add Signature Anchors Signature anchors are special template variables that tell TurboSign where to place signature elements (name, date, signature) in your generated documents. This guide walks you through adding them to your template. ## Prerequisites - A template uploaded to TurboDocx with signature variables (e.g., `{SalesSigner}`) - An e-signature automation configured (see [How to Setup E-Signature Automation](./signature-automation.md)) ## Add Signature Anchors ### Step 1: Select Your Template Navigate to your templates and select the template you want to configure with signature anchors. ![Select Template](/img/wrike-integration/FieldMap01-SelectTemplate.jpeg) ### Step 2: Open Template Preferences Click **Edit Template & Preferences** to open the template configuration view. ![Edit Template and Preferences](/img/wrike-integration/FieldMap02-EditTemplatePreferences.jpeg) ### Step 3: Edit the Signature Variable Click the edit icon on the signature variable you want to configure as an anchor (e.g., **SalesSigner**). ![Edit Signature Variable](/img/wrike-integration/SigAnchor04-OpenFieldTypeMenu.jpeg) ### Step 4: Open the Field Type Menu In the variable settings panel, click the **...** menu button next to the Default Value field to see the available field type options. ![Open Field Type Menu](/img/wrike-integration/SigAnchor05-ExpandFieldOptions.jpeg) ### Step 5: Select "Wrike Signature Field" Choose **Wrike Signature Field** from the dropdown. This marks the variable as a TurboSign signature anchor that will be used to place signature elements in the generated document. ![Select Wrike Signature Field](/img/wrike-integration/SigAnchor06-SelectWrikeSignatureField.jpeg) ### Step 6: Save Changes The field now shows **TurboSign Signature Anchor**. Click **Save Changes** to persist the configuration. ![Save Changes](/img/wrike-integration/SigAnchor07-SaveChanges.jpeg) Your signature anchor is now configured. The variable will show a **Signature Anchor** tag to confirm it's set up correctly. ![Signature Anchor Complete](/img/wrike-integration/SigAnchor08-Complete.jpeg) :::tip Repeat steps 3–6 for each signature variable in your template (e.g., `{SalesSignerName}`, `{SalesSignerSignature}`, `{ClientSignerName}`, etc.). ::: :::caution Anchor Names Must Match Your E-Signature Automation The signature anchor variable names you configure here **must exactly match** the anchor tags in your [How to Setup E-Signature Automation](./signature-automation.md). For example, if you set up `{SalesSignerSignature}` as an anchor here, the anchor tag in your e-signature automation must also be `{SalesSignerSignature}`. If they don't match, TurboSign won't be able to place the signature fields. See [How to Setup E-Signature Automation](./signature-automation.md) for how to configure the anchor tags on the automation side. ::: --- # How to Setup E-Signature Automation After [setting up your Wrike automation](./setting-up-automation.md) with a trigger status and folder, follow these steps to configure it to generate documents and automatically send them for e-signature. ## Configure E-Signature Automation ### Step 1: Select Send for E-Signature Click the **Send for e-signature** action card to select it as the automation action. ![Select E-Signature Action](/img/wrike-integration/SigAuto01-SelectESignature.jpeg) ### Step 2: Proceed to Signer Configuration Click **Next** to continue to the signer configuration step. ![Click Next](/img/wrike-integration/SigAuto02-ClickNext.jpeg) ## Configure Signers ### Step 3: Select the First Recipient's Email Field Select the Wrike field that contains the first recipient's email address. This field will be read from the Wrike task to determine who receives the signature request. ![Select Recipient Email](/img/wrike-integration/SigAuto03-RecipientEmail.jpeg) ### Step 4: Select the Recipient's Name Field (Optional) Optionally, choose the field that represents the name of the recipient. This will be used in the signature request email. ![Select Recipient Name](/img/wrike-integration/SigAuto04-RecipientName.jpeg) ### Step 5: Add Additional Signers (Optional) Optionally, add additional signers if your document requires multiple signatures. ![Add Additional Signers](/img/wrike-integration/SigAuto05-AddSigners.jpeg) ## Map Document Fields ### Step 6: Add a Document Field Click **Add Field** to begin mapping document fields that the signer will need to fill out. ![Add Field](/img/wrike-integration/SigAuto06-AddField.jpeg) ### Step 7: Choose Document Fields Click and choose the document field(s) that need to be completed by the signer. ![Choose Document Fields](/img/wrike-integration/SigAuto07-ChooseDocField.jpeg) ### Step 8: Select the Field Type Select the specific field type for the document field (e.g., signature, date, text). ![Select Field Type](/img/wrike-integration/SigAuto08-ChooseDocField2.jpeg) ### Step 9: Set the Anchor Tag Change the anchor tag to match the placeholder in your template (e.g., `{SalesSigner}`). This tells TurboSign where to place the field in the generated document. ![Set Anchor Tag](/img/wrike-integration/SigAuto09-ChangeAnchorTag.jpeg) :::caution Anchor Tags Must Match Your Template The anchor tag you set here **must exactly match** the corresponding variable in your document template. If they don't match, TurboSign won't be able to place the signature field. See [How to Add Signature Anchors](./signature-anchors.md) for how to configure these in your template. ::: ### Step 10: Map Additional Document Fields (Optional) Optionally, repeat the process to map more document fields for the signer. ![Map More Fields](/img/wrike-integration/SigAuto10-MapMoreFields.jpeg) ## Post-Signature Settings ### Step 11: Choose Where the Signed Document Gets Attached Select where the signed document should be attached after all signatures are complete. ![Attach Signed Document](/img/wrike-integration/SigAuto11-AttachSignedDoc.jpeg) ### Step 12: Choose a Post-Signature Task Status (Optional) Optionally, choose which status the triggering Wrike task should be changed to after the signature is completed. ![Choose Post-Signature Status](/img/wrike-integration/SigAuto12-ChooseStatus.jpeg) ### Step 13: Select the Completed Status Select the status to apply (e.g., **Completed**) so your Wrike workflow advances automatically after signing. ![Select Completed Status](/img/wrike-integration/SigAuto13-SelectCompleted.jpeg) ### Step 14: Configure Notifications (Optional) Optionally, choose who gets tagged or notified after a signature is completed. ![Configure Notifications](/img/wrike-integration/SigAuto14-Notifications.jpeg) ## Finalize the Automation ### Step 15: Proceed to Final Step Click **Next** to advance to the automation creation step. ![Click Next](/img/wrike-integration/SigAuto15-ClickNext.jpeg) ### Step 16: Create the Automation Click **Create Automation** to save and activate your e-signature automation workflow. ![Create Automation](/img/wrike-integration/SigAuto16-CreateAutomation.jpeg) ## What's Next? - **[How to Add Signature Anchors](./signature-anchors.md)** to your template if you haven't already - **[How to Setup Static Field Mapping](./field-mapping.md)** to template variables for static data - If something isn't working, see [Troubleshooting and FAQ](./troubleshooting.md) --- # End-to-End Example: Generate & Sign a Proposal This walkthrough follows a real scenario from start to finish. A project manager generates a proposal document from a Wrike task and sends it to a client for digital signature — all by changing task statuses. :::tip Want to set this up yourself? This page shows the workflow **in action**. For setup instructions, see the [guides below](#set-this-up-yourself). ::: ## The Scenario Your team uses Wrike to manage client service delivery projects. Each project has deliverables, timelines, and billing milestones tracked as tasks. When a deliverable is ready, you need to generate a branded proposal with a project timeline, pricing, and signature fields — then send it to the client for signing. With TurboDocx, this entire flow is triggered by two status changes in Wrike. ## Step 1: Open the Task Navigate to the task you want to generate a document for. In this example, we're working with **"Deliverable 1"** under a CWM Deployment project in the Service Delivery space. ![Open the task in Wrike](/img/wrike-integration/EndToEnd01-ClickTask.jpeg) ## Step 2: Trigger Document Generation :::info How to set this up See [How to Setup Document Generation Automation](./document-generation-automation.md) to configure the trigger status and template for your automation. ::: Change the task status to **"Generate Document"**. This is the trigger status configured in the TurboDocx automation — as soon as the status changes, TurboDocx picks it up. ![Change status to Generate Document](/img/wrike-integration/EndToEnd02-GenerateDocument.jpeg) ## Step 3: Document is Generated and Attached Within moments, the **TurboDocx Document Bot** generates the document and attaches it directly to the Wrike task. A comment confirms the file was created successfully — in this case, **"Wrike Proposal Estimates.docx"**. ![Document attached to task](/img/wrike-integration/EndToEnd03-DocumentAttached.jpeg) ## Step 4: Review the Generated Document :::info How to set this up See [How to Setup Static Field Mapping](./field-mapping.md) to map Wrike custom fields to template variables, and [How to Setup AI Variable Configuration](./ai-variable.md) to configure AI-generated content. ::: Click the attachment to open and review the proposal. Notice two things: - The **Project Timeline** table was generated by AI based on the project's tasks and dates - The **Projected Deliverable Total Revenue** ($36,000.00) was pulled directly from a Wrike custom field ![Review the generated proposal](/img/wrike-integration/EndToEnd04-ReviewDocument.jpeg) ## Step 5: Check the Signature Anchors Scroll down to the signature section. The document contains **signature anchor placeholders** like `{SalesSignerName}`, `{SalesSigner}`, `{ClientSignerName}`, and `{ClientSignerSignature}`. These aren't visible to the final signer — TurboSign replaces them with interactive signature fields. ![Signature anchors in the document](/img/wrike-integration/EndToEnd05-SignatureAnchors.jpeg) How are these anchors configured? In the TurboDocx template settings, each of these variables is marked as a **Wrike Signature Anchor**. This tells TurboSign to treat them as placement markers for signature fields rather than regular text. ![Anchor configuration in template settings](/img/wrike-integration/EndToEnd06-AnchorConfig.jpeg) For the full setup guide, see [How to Add Signature Anchors](./signature-anchors.md). How do anchor tags map to signature fields? In the e-signature automation configuration, each anchor tag is mapped to a specific signature field type and assigned to a recipient: - `{ClientSigner}` becomes a **Signature** box assigned to the 1st Signer (the customer) ![ClientSigner anchor mapped to signature field](/img/wrike-integration/EndToEnd07-ClientSignerAnchor.jpeg) - `{ClientDate}` becomes a **Date** field, and `{ClientFullName}` becomes a **Full Name** field — both auto-populated when the signer opens the document ![Date and Full Name anchor fields](/img/wrike-integration/EndToEnd08-DateAnchorField.jpeg) For the full setup guide, see [How to Setup E-Signature Automation](./signature-automation.md). Where does the recipient email come from? The signing request is sent to the email address in the **"Customer email"** custom field on the Wrike task. In this example, that's `nicolas@example.com`. This field is mapped in the e-signature automation's recipient configuration. ![Customer email field in Wrike task](/img/wrike-integration/EndToEnd09-RecipientEmail.jpeg) For details on mapping recipient fields, see [How to Setup E-Signature Automation](./signature-automation.md). Here's what it looks like once TurboSign places the interactive signature fields on the final document — the anchors from the template become drag-and-drop fields assigned to each recipient: ![TurboSign signature field placement](/img/wrike-integration/EndToEnd12-ReviewAndSend.jpeg) ## Step 6: Send for Signature :::info How to set this up See [How to Setup E-Signature Automation](./signature-automation.md) to configure the signature trigger, recipients, and anchor tag mapping. ::: Back in Wrike, change the task status to **"Send for Signature"**. This triggers the TurboSign signing workflow. ![Change status to Send for Signature](/img/wrike-integration/EndToEnd10-SendForSignature.jpeg) ## Step 7: Bot Posts Signing Status The TurboDocx Bot comments on the task with the signing status, the signing order (for documents with multiple signers), and a **review link** for you to verify the document before it's sent. ![Bot comment with signing status and review link](/img/wrike-integration/EndToEnd11-BotComment.jpeg) ## Step 8: Review and Send Click the review link to open TurboDocx's signature assignment view. Here you can verify that the signature fields (Full Name, Date, Signature) are correctly placed on the document. When everything looks right, click **"Send Document"** to deliver the signing request to the recipient's email. ![Review signature placements and send](/img/wrike-integration/EndToEnd12-ReviewAndSend.jpeg) ## Set This Up Yourself Ready to configure this workflow for your team? Follow these guides in order: | Step | Guide | What You'll Do | |------|-------|---------------| | 1 | [Setting Up a Wrike Automation](./setting-up-automation.md) | Connect Wrike and create your first automation | | 2 | [How to Setup Static Field Mapping](./field-mapping.md) | Map Wrike custom fields (revenue, dates) to template variables | | 3 | [How to Setup AI Variable Configuration](./ai-variable.md) | Set up AI-generated content like project timelines | | 4 | [How to Add Signature Anchors](./signature-anchors.md) | Mark template variables as signature anchor fields | | 5 | [How to Setup E-Signature Automation](./signature-automation.md) | Configure recipients, anchor tags, and post-signature actions | --- # Troubleshooting and FAQ ## Common Issues ### Document Generation Takes Too Long **Cause:** Complex templates or AI generation can take 1-2 minutes. **Solution:** - Wait at least 2 minutes before assuming failure - Simplify your template or reduce the number of AI-generated variables - Check that your Wrike task has sufficient data for the AI to work with ### Document Not Attached to Wrike Task **Cause:** Generation succeeded but attachment failed. **Solution:** 1. Check that your Wrike API token has permission to add attachments 2. Verify the Wrike folder/task still exists 3. Re-check your automation configuration in TurboDocx ### Status Not Triggering the Automation **Cause:** The task status changed to a status that doesn't match your automation's trigger. **Solution:** - Open TurboDocx and check which trigger statuses are configured on your automation - Ensure the Wrike status name matches exactly what you selected during setup - Verify the task is in a folder that the automation is monitoring ### Folder Permalink Verification Failed **Cause:** The Wrike folder URL is incorrect or inaccessible. **Solution:** 1. In Wrike, right-click the folder and select **Copy Link** to get a fresh permalink 2. Verify your Wrike API token has access to the folder 3. Paste the permalink into the automation configuration and try verifying again ### Signature Workflow Not Working **Cause:** Missing custom fields or document anchors. **Solution:** - Ensure `Seller email` and `Customer email` custom fields exist and are populated on the Wrike task - Verify the document template contains all required signature anchor fields - Check that TurboSign is configured in your organization - See the [End-to-End Example](./signature-workflow.md) for full requirements ## Security Best Practices - **Rotate API keys regularly**: Update your Wrike API token periodically - **Monitor automation activity**: Review your automations in TurboDocx settings - **Use least-privilege access**: Only grant the minimum Wrike permissions needed - **Secure your API tokens**: Never share or commit API keys to version control ## FAQ ### Can I use multiple templates with the same Wrike folder? Yes! You can create multiple automations for the same folder, each with a different template and trigger status. Use different trigger statuses to control which automation fires. ### Can I use this with Wrike tasks or only folders? Both! The automation monitors a specific folder, but the status changes happen on tasks within that folder. You can also monitor entire spaces or projects. ### How do I know if document generation failed? If document generation fails, the document won't appear as an attachment on your Wrike task. Check your automation configuration and ensure the Wrike task has sufficient data for the template. ### Can the AI use data from multiple Wrike tasks? Yes! If you trigger from a folder, the AI can access all tasks within that folder. If you trigger from a task, it can access parent/child tasks as well. ### How do I edit or delete an automation? Open the Wrike integration configuration in TurboDocx (Settings > Integrations > Wrike). You can manage all your automations from the configuration panel. ### Can I create automations for different workflows? Yes. Each automation is independent — you can set up different trigger statuses, folders, and templates for each one. For example, one automation for proposals and another for SOWs. :::tip Need Help? Join our [Discord community](https://discord.gg/turbodocx) for support! Our team and community members are ready to help you troubleshoot any issues. ::: --- # Zapier Integration The Zapier integration in TurboDocx enables you to connect your document generation workflow to thousands of apps and services through Zapier's powerful automation platform. This integration allows you to export TurboDocx deliverables and trigger automated workflows across any Zapier-supported application. ## Overview With the Zapier integration, you can seamlessly export your generated documents and data from TurboDocx to any of the 5,000+ apps supported by Zapier. This opens up endless possibilities for automating your document workflows and connecting TurboDocx to your existing business systems. ## Key Features ### Export to Any Zapier-Supported App - **CRM Systems**: Automatically send generated proposals to Salesforce, HubSpot, or Pipedrive - **Project Management**: Create tasks in Asana, Trello, or Monday.com when documents are generated - **Communication**: Send notifications through Slack, Microsoft Teams, or email when deliverables are ready - **Cloud Storage**: Automatically save documents to Google Drive, Dropbox, or OneDrive - **Database Systems**: Store document metadata in Airtable, Google Sheets, or other databases ### Automated Workflows - Trigger actions based on document generation events - Set up multi-step workflows that involve multiple applications - Create conditional logic based on document properties or content - Schedule automated document exports and distributions ## Common Use Cases ### Sales & Marketing - Generate proposals in TurboDocx and automatically send them to prospects via email - Create contracts and sync client data to your CRM system - Generate marketing materials and distribute them across multiple channels ### Operations & Project Management - Create project documentation and automatically create corresponding tasks in project management tools - Generate reports and send them to stakeholders via preferred communication channels - Synchronize document metadata with operational databases ### HR & Administration - Generate employee documents and automatically file them in HR systems - Create onboarding materials and trigger welcome sequences - Generate compliance documents and log them in tracking systems ## Getting Started The Zapier integration is available as part of TurboDocx's premium features. To set up and configure the Zapier integration for your organization: ### Contact Sales To enable the Zapier integration and discuss your specific automation needs: - **Email**: [team@turbodocx.com](mailto:team@turbodocx.com) - **Schedule a Demo**: Book a personalized demonstration to see how Zapier integration can streamline your workflows - **Custom Setup**: Our team can help design custom automation workflows tailored to your business processes ### What Our Team Will Help You With - **Integration Setup**: Complete configuration of the Zapier connection - **Workflow Design**: Custom automation workflows based on your specific needs - **Testing & Optimization**: Ensuring your integrations work seamlessly - **Training**: Comprehensive training for your team on managing Zapier automations - **Ongoing Support**: Dedicated support for troubleshooting and optimization ## Benefits ### Increased Efficiency - Eliminate manual document distribution and filing - Reduce time spent on repetitive tasks - Ensure consistent document workflows across your organization ### Enhanced Collaboration - Automatically notify team members when documents are ready - Sync document data across multiple platforms - Create unified workflows that span multiple applications ### Improved Accuracy - Reduce human error in document handling - Ensure consistent data entry across systems - Maintain audit trails of document lifecycle ## Technical Requirements - TurboDocx Enterprise or Premium plan - Active Zapier account (Basic or Premium recommended) - Administrative access to configure integrations - Access to target applications for integration setup ## Next Steps Ready to supercharge your document workflows with Zapier integration? Contact our sales team today to: 1. **Schedule a consultation** to discuss your specific automation needs 2. **See a live demo** of TurboDocx and Zapier working together 3. **Get a custom quote** based on your integration requirements 4. **Plan your implementation** with our integration specialists Transform your document generation process into a fully automated workflow that connects seamlessly with your existing business tools through the power of Zapier integration. --- ## Zoom Integration # Turn Your Zoom Meetings into Professional Documents & Presentations Transform your meeting recordings into professional documents automatically. TurboDocx's integration eliminates manual note-taking by converting meeting transcripts into polished deliverables, streamlining your post-meeting workflow. ## What You Can Create - **📄 Meeting Summaries**: Convert discussions into formatted meeting minutes and action items - **📊 Client Presentations**: Turn discovery calls into compelling presentation decks - **📋 Project Proposals**: Use requirement gathering sessions to create detailed proposals - **📝 Follow-up Reports**: Generate comprehensive meeting reports with key takeaways - **💼 Sales Materials**: Transform prospect calls into customized sales presentations - **🔄 Automated Workflows**: Connect meeting transcripts directly to your document templates ## Before You Begin To use the Zoom integration, you'll need: - A Zoom account with **cloud recording** enabled - Administrative access to create a Zoom app (or assistance from your IT team) - Meeting recordings stored in Zoom's cloud (local recordings are not accessible) :::tip If you're not sure whether you have cloud recording enabled, check with your Zoom administrator or look for the "Cloud" option when you start recording a meeting. ::: ## Step 1: Creating Your Zoom App The first step is to create a Zoom app that will allow TurboDocx to access your meeting transcripts. Don't worry - this is a one-time setup process that we'll walk you through step by step. ### Navigate to the Zoom Marketplace 1. Open your web browser and go to [https://marketplace.zoom.us](https://marketplace.zoom.us) 2. Click **"Sign In"** in the top right corner ![Zoom Marketplace Sign In](/img/zoom_integration/step1.png) 3. Sign in with your Zoom account credentials 5. You'll be taken to [https://marketplace.zoom.us/user/build](https://marketplace.zoom.us/user/build) ### Create a New App 1. In the top right corner, click the dropdown that says **"Develop"** 2. Select **"Build App"** from the dropdown ![Step 2: Build App selection](/img/zoom_integration/step2.png) 3. In the dialog that appears, select **"General App"** ![Step 3: General App selection](/img/zoom_integration/step3.png) 4. Click **"Create"** 5. **Rename your app to "TurboDocx"** ![Step 4: Rename app to TurboDocx](/img/zoom_integration/Step4RenameTurboDocx.png) :::info Why General App? We use a "General App" because it provides the specific permissions needed to access your cloud recordings safely and securely. ::: ### Configure Basic Information After creating your app, you'll be taken to the app configuration page. Let's set up the basic information first. #### Basic Information Tab 1. Make sure you're on the **"Basic Information"** tab 2. **Leave app as User Managed** - Ensure the app type remains set as "User Managed" ![Basic Information User Managed](/img/zoom_integration/Step5UserManaged.png) 3. Scroll down to **"App Credentials"** section 4. **Note down the Client ID and Client Secret** - You'll need these values when configuring TurboDocx ![App Credentials Section](/img/zoom_integration/Step6ClientIdAndSecret.png) :::tip Keep Your Credentials Safe Your Client Secret is like a password - never share it publicly or include it in emails. TurboDocx will store it securely once you enter it. ::: #### OAuth Information 1. Still on the Basic Information tab, scroll down to **"OAuth Information"** 2. For **"Redirect URL for OAuth"**, enter: ``` https://api.turbodocx.com/oauth/zoom/callback ``` 3. Under **"OAuth Allow List"**, add the following URLs (click "Add" for each one): ``` https://app.turbodocx.com ``` ![OAuth Allow List Configuration](/img/zoom_integration/oauth_allowlist.png) ### Configure Scopes Now we need to set up the permissions (scopes) that allow TurboDocx to access your cloud recordings. #### Scopes Tab 1. In the left bar, click on the **"Scopes"** button and navigate to the add scopes page ![Navigate to Scopes Section](/img/zoom_integration/NavigateToScopesSection.png) 2. Click **"Add Scopes"** button ![Click Add Scopes Button](/img/zoom_integration/clickaddscopesbutton.png) 3. In the search dialog that appears, search for: ``` List all cloud recordings for a user ``` Or look for the scope with this value: ``` cloud_recording:read:list_user_recordings ``` ![Type List Cloud Recordings For User](/img/zoom_integration/TypeListCloudRecordingsForUser.png) 4. **Add the scope** labeled "View your recordings" (with value `cloud_recording:read:list_user_recordings`) ![Select Scope and Press Done](/img/zoom_integration/SelectScopeandPressDone.png) 5. Click **"Done"** to save the configuration ## Step 2: Configuring TurboDocx ### 🔧 How to Configure Zoom in Organization Settings 1. Go to **Settings** ![Go to Settings](/img/zoom_integration/GoToSettings.png) 2. Click on **Organization Settings** ![Go to Organization Settings](/img/zoom_integration/GoToOrganizationSettings.png) 3. Scroll down to the **Zoom** section 4. Click **Configure Zoom** ![Click Configure Zoom](/img/zoom_integration/ClickConfigureZoom.png) 5. A Zoom Configuration pop-up will appear 6. Take the **Client ID** and **Client Secret** you obtained earlier, paste them into the appropriate fields, and click **Save Configuration** in the bottom right-hand corner ![Enter Client ID Secret and Press Save Configuration](/img/zoom_integration/enterclientidsecretandpresssaveconfiguration.png) ### Alternative: Inline Configuration Alternatively, you can configure Zoom integration directly when you first access transcript providers. When you navigate to the transcript providers section and select Zoom, you'll be prompted to enter your credentials if the integration hasn't been configured yet. Simply enter your Client ID and Client Secret in the configuration dialog that appears and save your settings. ## Step 3: Using Your Zoom Integration Congratulations! Your Zoom integration is now ready to use. Here's how to access your meeting transcripts and use them in your documents. ### Accessing Meeting Transcripts 1. Click the source dropdown and click **"Change Source"** 2. Go to the **App Library** tab 3. Click **"Transcript Providers"** ![Meeting Providers](/img/zoom_integration/MeetingProviders.png) 4. Click **"Zoom"** and then you should be able to click the transcript 5. You'll see a list of your recent cloud recordings with transcripts ![Transcripts Shown](/img/zoom_integration/TranscriptsShown.png) ## Troubleshooting If you're having trouble with your Zoom integration, here are some common issues and their solutions. ### "No Meetings Found" - **Cause**: No cloud recordings with transcripts available - **Solution**: Ensure your meetings are recorded to the cloud and have transcription enabled ### "Authentication Failed" - **Cause**: Incorrect credentials or expired authentication - **Solution**: - Double-check your Client ID and Client Secret - Try re-authenticating by clicking "Authenticate with Zoom" again ### "Permission Denied" - **Cause**: Required scope not properly configured - **Solution**: Verify that `cloud_recording:read:list_user_recordings` scope is added to your Zoom app :::tip Getting Help If you continue to experience issues: - Verify all steps in this guide have been completed - Check that your Zoom account has cloud recording enabled - Ensure you have meeting recordings stored in Zoom's cloud - Contact your TurboDocx support team for assistance ::: ## Security and Privacy Your data security and privacy are important to us. Here's how your information is protected when using the Zoom integration. ### How Your Data is Protected - **Secure Authentication**: TurboDocx uses OAuth 2.0, the industry standard for secure API access - **Limited Permissions**: The integration only requests access to read your cloud recordings - **Encrypted Transmission**: All data transfers are encrypted using industry-standard protocols ## Best Practices ### For Optimal Results - **Enable Transcription**: Always enable transcription when recording meetings to the cloud - **Use Clear Audio**: Ensure good audio quality for more accurate transcripts - **Descriptive Meeting Names**: Use clear, descriptive names for your meetings to easily identify them later - **Regular Cleanup**: Periodically review and organize your cloud recordings ### Meeting Recording Tips - Start recording at the beginning of important meetings - Speak clearly and avoid overlapping conversations - Use a good quality microphone when possible - Consider recording in a quiet environment ## Finished Congratulations on setting up your Zoom integration! You can now seamlessly import meeting transcripts and use them to create professional documents with TurboDocx. --- # Go SDK The official TurboDocx Deliverable SDK for Go applications. Generate documents from templates with dynamic variable injection, download source files and PDFs, and manage deliverables programmatically with idiomatic Go patterns, context support, and comprehensive error handling. Available as `github.com/TurboDocx/SDK/packages/go-sdk`. ## Installation ```bash go get github.com/TurboDocx/SDK/packages/go-sdk ``` ## Requirements - Go 1.21+ --- ## Configuration ```go package main "log" "os" sdk "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { // Option 1: Standalone deliverable client (no SenderEmail needed) deliverable, err := sdk.NewDeliverableClientOnly(sdk.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } // Option 2: Full client (includes TurboSign + Deliverable) client, err := sdk.NewClientWithConfig(sdk.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), SenderEmail: "sender@example.com", BaseURL: "https://api.turbodocx.com", // Optional custom base URL }) if err != nil { log.Fatal(err) } deliverable = client.Deliverable } ``` :::tip No SenderEmail Required Use `NewDeliverableClientOnly()` when you only need document generation — it skips the `SenderEmail` validation required by TurboSign. ::: ### Environment Variables ```bash export TURBODOCX_API_KEY=your_api_key_here export TURBODOCX_ORG_ID=your_org_id_here ``` --- ## Quick Start ### Generate a document from a template ```go package main "context" "encoding/json" "fmt" "log" "os" sdk "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { deliverable, err := sdk.NewDeliverableClientOnly(sdk.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } ctx := context.Background() result, err := deliverable.GenerateDeliverable(ctx, &sdk.CreateDeliverableRequest{ Name: "Q1 Report", TemplateID: "your-template-id", Variables: []sdk.DeliverableVariable{ {Placeholder: "{CompanyName}", Text: "Acme Corporation", MimeType: "text"}, {Placeholder: "{Date}", Text: "2026-03-12", MimeType: "text"}, }, Description: "Quarterly business report", Tags: []string{"reports", "quarterly"}, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(result, "", " "); fmt.Println("Result:", string(b)) } ``` ### Download and manage deliverables ```go package main "context" "encoding/json" "fmt" "log" "os" sdk "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { deliverable, err := sdk.NewDeliverableClientOnly(sdk.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } ctx := context.Background() // List deliverables with pagination list, err := deliverable.ListDeliverables(ctx, &sdk.ListDeliverablesOptions{ Limit: 10, ShowTags: true, }) if err != nil { log.Fatal(err) } fmt.Printf("Total: %d\n", list.TotalRecords) // Get deliverable details details, err := deliverable.GetDeliverableDetails(ctx, "deliverable-uuid", &sdk.GetDeliverableOptions{ ShowTags: true, }) if err != nil { log.Fatal(err) } fmt.Printf("Name: %s\n", details.Name) // Download source file (DOCX/PPTX) sourceData, err := deliverable.DownloadSourceFile(ctx, "deliverable-uuid") if err != nil { log.Fatal(err) } os.WriteFile("report.docx", sourceData, 0644) // Download PDF pdfData, err := deliverable.DownloadPDF(ctx, "deliverable-uuid") if err != nil { log.Fatal(err) } os.WriteFile("report.pdf", pdfData, 0644) // Update deliverable updateResult, err := deliverable.UpdateDeliverableInfo(ctx, "deliverable-uuid", &sdk.UpdateDeliverableRequest{ Name: "Q1 Report - Final", Description: "Final quarterly business report", Tags: &[]string{"reports", "final"}, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(updateResult, "", " "); fmt.Println("Result:", string(b)) // Delete deliverable deleteResult, err := deliverable.DeleteDeliverable(ctx, "deliverable-uuid") if err != nil { log.Fatal(err) } b, _ = json.MarshalIndent(deleteResult, "", " "); fmt.Println("Result:", string(b)) } ``` --- ## Variable Types The Deliverable module supports four variable types for template injection: ### 1. Text Variables Inject plain text values into template placeholders: ```go variables := []sdk.DeliverableVariable{ {Placeholder: "{CompanyName}", Text: "Acme Corporation", MimeType: "text"}, {Placeholder: "{Date}", Text: "2026-03-12", MimeType: "text"}, } ``` ### 2. HTML Variables Inject rich HTML content with formatting: ```go variables := []sdk.DeliverableVariable{ { Placeholder: "{Summary}", Text: "This is a formatted summary with rich text.", MimeType: "html", }, } ``` ### 3. Image Variables Inject images by providing a URL or base64-encoded content: ```go variables := []sdk.DeliverableVariable{ { Placeholder: "{Logo}", Text: "https://example.com/logo.png", MimeType: "image", }, } ``` ### 4. Markdown Variables Inject markdown content that gets converted to formatted text: ```go variables := []sdk.DeliverableVariable{ { Placeholder: "{Notes}", Text: "## Key Points\n- First item\n- Second item\n\n**Important:** Review before submission.", MimeType: "markdown", }, } ``` :::info Variable Stack For repeating content (e.g., table rows), use `VariableStack` instead of `Text` to provide multiple values for the same placeholder. See the [Types section](#createdeliverablerequest) for details. ::: --- ## API Reference ### Configure Create a new TurboDocx Deliverable client. ```go // Standalone deliverable client (no SenderEmail needed) deliverable, err := sdk.NewDeliverableClientOnly(sdk.ClientConfig{ APIKey: "your-api-key", OrgID: "your-org-id", }) // Full client (includes TurboSign + Deliverable) client, err := sdk.NewClientWithConfig(sdk.ClientConfig{ APIKey: "your-api-key", OrgID: "your-org-id", SenderEmail: "sender@example.com", BaseURL: "https://api.turbodocx.com", // Optional }) deliverable := client.Deliverable ``` :::caution API Credentials Required Both `APIKey` and `OrgID` parameters are **required** for all API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: ### Generate deliverable Generate a new document from a template with variable substitution. ```go result, err := deliverable.GenerateDeliverable(ctx, &sdk.CreateDeliverableRequest{ Name: "Q1 Report", TemplateID: "your-template-id", Variables: []sdk.DeliverableVariable{ {Placeholder: "{CompanyName}", Text: "Acme Corp", MimeType: "text"}, {Placeholder: "{Date}", Text: "2026-03-12", MimeType: "text"}, }, Description: "Quarterly business report", Tags: []string{"reports", "quarterly"}, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(result, "", " "); fmt.Println("Result:", string(b)) ``` ### List deliverables List deliverables with pagination, search, and filtering. ```go list, err := deliverable.ListDeliverables(ctx, &sdk.ListDeliverablesOptions{ Limit: 10, Offset: 0, Query: "report", ShowTags: true, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(list, "", " "); fmt.Println("Result:", string(b)) ``` ### Get deliverable details Retrieve the full details of a single deliverable, including variables and fonts. ```go details, err := deliverable.GetDeliverableDetails(ctx, "deliverable-uuid", &sdk.GetDeliverableOptions{ ShowTags: true, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(details, "", " "); fmt.Println("Result:", string(b)) ``` ### Update deliverable info Update a deliverable's name, description, or tags. ```go result, err := deliverable.UpdateDeliverableInfo(ctx, "deliverable-uuid", &sdk.UpdateDeliverableRequest{ Name: "Q1 Report - Final", Description: "Final quarterly business report", Tags: &[]string{"reports", "final"}, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(result, "", " "); fmt.Println("Result:", string(b)) ``` ### Delete deliverable Soft-delete a deliverable. ```go result, err := deliverable.DeleteDeliverable(ctx, "deliverable-uuid") if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(result, "", " "); fmt.Println("Result:", string(b)) ``` ### Download source file Download the original source file (DOCX or PPTX). ```go sourceData, err := deliverable.DownloadSourceFile(ctx, "deliverable-uuid") if err != nil { log.Fatal(err) } // Save to file err = os.WriteFile("report.docx", sourceData, 0644) if err != nil { log.Fatal(err) } ``` ### Download PDF Download the PDF version of a deliverable. ```go pdfData, err := deliverable.DownloadPDF(ctx, "deliverable-uuid") if err != nil { log.Fatal(err) } // Save to file err = os.WriteFile("report.pdf", pdfData, 0644) if err != nil { log.Fatal(err) } ``` --- ## Error Handling The SDK provides typed errors for different error scenarios: ### Error Types | Error Type | Status Code | Description | | --------------------- | ----------- | ---------------------------------- | | `TurboDocxError` | varies | Base error type for all API errors | | `AuthenticationError` | 401 | Invalid or missing API key | | `AuthorizationError` | 403 | Authenticated but lacks required permissions | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Deliverable or template not found | | `ConflictError` | 409 | Request conflicts with current resource state | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | ### Handling Errors ```go "errors" "log" sdk "github.com/TurboDocx/SDK/packages/go-sdk" ) result, err := deliverable.GenerateDeliverable(ctx, request) if err != nil { // Check for specific error types var authErr *sdk.AuthenticationError var validationErr *sdk.ValidationError var notFoundErr *sdk.NotFoundError var rateLimitErr *sdk.RateLimitError var networkErr *sdk.NetworkError switch { case errors.As(err, &authErr): log.Printf("Authentication failed: %s", authErr.Message) case errors.As(err, &validationErr): log.Printf("Validation error: %s", validationErr.Message) case errors.As(err, ¬FoundErr): log.Printf("Not found: %s", notFoundErr.Message) case errors.As(err, &rateLimitErr): log.Printf("Rate limited: %s", rateLimitErr.Message) case errors.As(err, &networkErr): log.Printf("Network error: %s", networkErr.Message) default: // Base TurboDocxError or unexpected error var turboErr *sdk.TurboDocxError if errors.As(err, &turboErr) { log.Printf("API error [%d]: %s", turboErr.StatusCode, turboErr.Message) } else { log.Fatal(err) } } } ``` ### Error Properties | Property | Type | Description | | ------------ | -------- | ---------------------------- | | `Message` | `string` | Human-readable error message | | `StatusCode` | `int` | HTTP status code | | `Code` | `string` | Error code (if available) | --- ## Types ### DeliverableVariable Variable configuration for template injection: | Property | Type | Required | Description | | ------------------------ | ----------------------- | -------- | ---------------------------------------------------- | | `Placeholder` | `string` | Yes | Template placeholder (e.g., `{CompanyName}`) | | `Text` | `string` | No\* | Value to inject | | `MimeType` | `string` | Yes | `"text"`, `"html"`, `"image"`, or `"markdown"` | | `IsDisabled` | `bool` | No | Skip this variable during generation | | `Subvariables` | `[]DeliverableVariable` | No | Nested sub-variables for HTML content | | `VariableStack` | `interface{}` | No | Multiple instances for repeating content | | `AIPrompt` | `string` | No | AI prompt for content generation (max 16,000 chars) | | `AllowRichTextInjection` | `bool` | No | Whether to allow rich text injection | \*Required unless `VariableStack` is provided or `IsDisabled` is true. ### CreateDeliverableRequest Request configuration for `GenerateDeliverable`: | Property | Type | Required | Description | | -------------- | ----------------------- | -------- | ------------------------------------------ | | `Name` | `string` | Yes | Deliverable name (3-255 characters) | | `TemplateID` | `string` | Yes | Template ID to generate from | | `Variables` | `[]DeliverableVariable` | Yes | Variables for template substitution | | `Description` | `string` | No | Description (up to 65,535 characters) | | `Tags` | `[]string` | No | Tag strings to associate | ### UpdateDeliverableRequest Request configuration for `UpdateDeliverableInfo`: | Property | Type | Required | Description | | ------------- | ----------- | -------- | ---------------------------------------------------------------- | | `Name` | `string` | No | Updated name (3-255 characters) | | `Description` | `string` | No | Updated description | | `Tags` | `*[]string` | No | Replace all tags (`nil` = no change, `&[]string{}` = remove all) | ### ListDeliverablesOptions Options for `ListDeliverables`: | Property | Type | Required | Description | | -------------- | ---------- | -------- | ------------------------------------ | | `Limit` | `int` | No | Results per page (1-100, default 6) | | `Offset` | `int` | No | Results to skip (default 0) | | `Query` | `string` | No | Search query to filter by name | | `ShowTags` | `bool` | No | Include tags in the response | ### DeliverableRecord The deliverable object returned by both `ListDeliverables` and `GetDeliverableDetails`: | Property | Type | Description | | -------------------- | ----------------------- | ---------------------------------------- | | `ID` | `string` | Unique deliverable ID (UUID) | | `Name` | `string` | Deliverable name | | `Description` | `string` | Description text | | `TemplateID` | `string` | Source template ID | | `TemplateName` | `string` | Source template name | | `TemplateNotDeleted` | `*bool` | Whether the source template still exists | | `CreatedBy` | `string` | User ID of the creator | | `Email` | `string` | Creator's email address | | `FileSize` | `int64` | File size in bytes | | `FileType` | `string` | MIME type of the generated file | | `DefaultFont` | `string` | Default font used | | `Fonts` | `[]Font` | Fonts used in the document | | `IsActive` | `bool` | Whether the deliverable is active | | `CreatedOn` | `string` | ISO 8601 creation timestamp | | `UpdatedOn` | `string` | ISO 8601 last update timestamp | | `Variables` | `[]DeliverableVariable` | Parsed variable objects with values | | `Tags` | `[]Tag` | Associated tags (when `ShowTags=true`) | ### Tag Tag object included when `ShowTags` is enabled: | Property | Type | Description | | ----------- | -------- | ------------------------------------ | | `ID` | `string` | Tag unique identifier (UUID) | | `Label` | `string` | Tag display name | | `IsActive` | `bool` | Whether the tag is active | | `UpdatedOn` | `string` | ISO 8601 last update timestamp | | `CreatedOn` | `string` | ISO 8601 creation timestamp | | `CreatedBy` | `string` | User ID of the tag creator | | `OrgID` | `string` | Organization ID | --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[TurboDocx Templating](/docs/TurboDocx%20Templating/How%20to%20Create%20a%20Template)** - How to create and configure document templates - **[Variable Reference](/docs/API/Deliverable%20API#variable-object-structure)** - Complete guide to variable types, formatting, and advanced injection options - **[API Reference](/docs/API/Deliverable%20API)** - Full REST API documentation for Deliverable endpoints --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) - [API Reference](/docs/API/Deliverable%20API) - [Webhook Configuration](/docs/TurboSign/Webhooks) --- # Java SDK The official TurboDocx Deliverable SDK for Java applications. Generate documents from templates with dynamic variable injection, download source files and PDFs, and manage deliverables programmatically with the Builder pattern, comprehensive error handling, and type-safe APIs. Available on Maven Central as `com.turbodocx:turbodocx-sdk`. ## Installation ```xml com.turbodocx turbodocx-sdk 0.4.0 ``` ```kotlin implementation("com.turbodocx:turbodocx-sdk:0.4.0") ``` ```groovy implementation 'com.turbodocx:turbodocx-sdk:0.4.0' ``` ## Requirements - Java 11+ - OkHttp 4.x (included) - Gson 2.x (included) --- ## Configuration ```java public class Main { public static void main(String[] args) { // Option 1: Standalone deliverable client (no senderEmail needed) DeliverableClient deliverable = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .buildDeliverableClient(); // Option 2: Full client (includes TurboSign + Deliverable) TurboDocxClient client = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .senderEmail("sender@example.com") .build(); DeliverableClient deliverable = client.deliverable(); } } ``` :::tip No senderEmail Required Use `buildDeliverableClient()` when you only need document generation — it skips the `senderEmail` validation required by TurboSign. ::: ### Environment Variables ```bash export TURBODOCX_API_KEY=your_api_key_here export TURBODOCX_ORG_ID=your_org_id_here ``` :::caution API Credentials Required Both `apiKey` and `orgId` parameters are **required** for all API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Generate a document from a template ```java public class Main { public static void main(String[] args) throws Exception { DeliverableClient deliverable = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .buildDeliverableClient(); Gson gson = new GsonBuilder().setPrettyPrinting().create(); DeliverableVariable var1 = new DeliverableVariable(); var1.setPlaceholder("{CompanyName}"); var1.setText("Acme Corporation"); var1.setMimeType("text"); DeliverableVariable var2 = new DeliverableVariable(); var2.setPlaceholder("{Date}"); var2.setText("2026-03-12"); var2.setMimeType("text"); CreateDeliverableRequest request = new CreateDeliverableRequest(); request.setName("Q1 Report"); request.setTemplateId("your-template-id"); request.setVariables(List.of(var1, var2)); request.setDescription("Quarterly business report"); request.setTags(List.of("reports", "quarterly")); CreateDeliverableResponse result = deliverable.generateDeliverable(request); System.out.println("Result: " + gson.toJson(result)); } } ``` ### Download and manage deliverables ```java public class Main { public static void main(String[] args) throws Exception { DeliverableClient deliverable = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .buildDeliverableClient(); Gson gson = new GsonBuilder().setPrettyPrinting().create(); // List deliverables with pagination ListDeliverablesRequest listRequest = new ListDeliverablesRequest(); listRequest.setLimit(10); listRequest.setShowTags(true); DeliverableListResponse list = deliverable.listDeliverables(listRequest); System.out.println("Total: " + list.getTotalRecords()); // Get deliverable details DeliverableRecord details = deliverable.getDeliverableDetails("deliverable-uuid"); System.out.println("Name: " + details.getName()); // Download source file (DOCX/PPTX) byte[] sourceFile = deliverable.downloadSourceFile("deliverable-uuid"); Files.write(Paths.get("report.docx"), sourceFile); // Download PDF byte[] pdfFile = deliverable.downloadPDF("deliverable-uuid"); Files.write(Paths.get("report.pdf"), pdfFile); // Update deliverable UpdateDeliverableRequest updateRequest = new UpdateDeliverableRequest(); updateRequest.setName("Q1 Report - Final"); updateRequest.setDescription("Final quarterly business report"); updateRequest.setTags(List.of("reports", "final")); deliverable.updateDeliverableInfo("deliverable-uuid", updateRequest); // Delete deliverable deliverable.deleteDeliverable("deliverable-uuid"); } } ``` --- ## Variable Types The Deliverable module supports four variable types for template injection: ### 1. Text Variables Inject plain text values into template placeholders: ```java DeliverableVariable var = new DeliverableVariable(); var.setPlaceholder("{CompanyName}"); var.setText("Acme Corporation"); var.setMimeType("text"); ``` ### 2. HTML Variables Inject rich HTML content with formatting: ```java DeliverableVariable var = new DeliverableVariable(); var.setPlaceholder("{Summary}"); var.setText("This is a formatted summary with rich text."); var.setMimeType("html"); ``` ### 3. Image Variables Inject images by providing a URL or base64-encoded content: ```java DeliverableVariable var = new DeliverableVariable(); var.setPlaceholder("{Logo}"); var.setText("https://example.com/logo.png"); var.setMimeType("image"); ``` ### 4. Markdown Variables Inject markdown content that gets converted to formatted text: ```java DeliverableVariable var = new DeliverableVariable(); var.setPlaceholder("{Notes}"); var.setText("## Key Points\n- First item\n- Second item\n\n**Important:** Review before submission."); var.setMimeType("markdown"); ``` :::info Variable Stack For repeating content (e.g., table rows), use `setVariableStack()` instead of `setText()` to provide multiple values for the same placeholder. See the [Types section](#createdeliverablerequest) for details. ::: --- ## API Reference ### Configure Create a new Deliverable client using the Builder pattern. ```java // Standalone deliverable client DeliverableClient deliverable = new TurboDocxClient.Builder() .apiKey("your-api-key") // Required .orgId("your-org-id") // Required .buildDeliverableClient(); // Or from the full client TurboDocxClient client = new TurboDocxClient.Builder() .apiKey("your-api-key") // Required .orgId("your-org-id") // Required .senderEmail("sender@co.com") // Required for TurboSign .build(); DeliverableClient deliverable = client.deliverable(); ``` ### Generate deliverable Generate a new document from a template with variable substitution. ```java DeliverableVariable var1 = new DeliverableVariable(); var1.setPlaceholder("{CompanyName}"); var1.setText("Acme Corp"); var1.setMimeType("text"); CreateDeliverableRequest request = new CreateDeliverableRequest(); request.setName("Q1 Report"); request.setTemplateId("your-template-id"); request.setVariables(List.of(var1)); request.setDescription("Quarterly business report"); request.setTags(List.of("reports", "quarterly")); CreateDeliverableResponse result = deliverable.generateDeliverable(request); System.out.println("Result: " + gson.toJson(result)); ``` ### List deliverables List deliverables with pagination, search, and filtering. ```java ListDeliverablesRequest request = new ListDeliverablesRequest(); request.setLimit(10); request.setOffset(0); request.setQuery("report"); request.setShowTags(true); DeliverableListResponse list = deliverable.listDeliverables(request); System.out.println("Result: " + gson.toJson(list)); ``` ### Get deliverable details Retrieve the full details of a single deliverable, including variables and fonts. ```java DeliverableRecord details = deliverable.getDeliverableDetails("deliverable-uuid", true); System.out.println("Result: " + gson.toJson(details)); ``` ### Update deliverable info Update a deliverable's name, description, or tags. ```java UpdateDeliverableRequest request = new UpdateDeliverableRequest(); request.setName("Q1 Report - Final"); request.setDescription("Final quarterly business report"); request.setTags(List.of("reports", "final")); UpdateDeliverableResponse result = deliverable.updateDeliverableInfo("deliverable-uuid", request); System.out.println("Result: " + gson.toJson(result)); ``` ### Delete deliverable Soft-delete a deliverable. ```java DeleteDeliverableResponse result = deliverable.deleteDeliverable("deliverable-uuid"); System.out.println("Result: " + gson.toJson(result)); ``` ### Download source file Download the original source file (DOCX or PPTX). ```java byte[] sourceData = deliverable.downloadSourceFile("deliverable-uuid"); // Save to file Files.write(Paths.get("report.docx"), sourceData); ``` ### Download PDF Download the PDF version of a deliverable. ```java byte[] pdfData = deliverable.downloadPDF("deliverable-uuid"); // Save to file Files.write(Paths.get("report.pdf"), pdfData); ``` --- ## Error Handling The SDK provides typed exceptions for different error scenarios: ### Error Types | Error Type | Status Code | Description | | -------------------------------------------- | ----------- | ---------------------------------- | | `TurboDocxException` | varies | Base exception for all API errors | | `TurboDocxException.AuthenticationException` | 401 | Invalid or missing API credentials | | `TurboDocxException.AuthorizationException` | 403 | Insufficient permissions | | `TurboDocxException.ValidationException` | 400 | Invalid request parameters | | `TurboDocxException.NotFoundException` | 404 | Deliverable or template not found | | `TurboDocxException.ConflictException` | 409 | Resource conflict | | `TurboDocxException.RateLimitException` | 429 | Too many requests | | `TurboDocxException.NetworkException` | - | Network connectivity issues | ### Handling Errors ```java try { CreateDeliverableResponse result = deliverable.generateDeliverable(request); } catch (TurboDocxException.AuthenticationException e) { System.err.println("Authentication failed: " + e.getMessage()); // Check your API key and org ID } catch (TurboDocxException.ValidationException e) { System.err.println("Validation error: " + e.getMessage()); // Check request parameters } catch (TurboDocxException.NotFoundException e) { System.err.println("Not found: " + e.getMessage()); // Template or deliverable doesn't exist } catch (TurboDocxException.RateLimitException e) { System.err.println("Rate limited: " + e.getMessage()); // Wait and retry } catch (TurboDocxException.NetworkException e) { System.err.println("Network error: " + e.getMessage()); // Check connectivity } catch (TurboDocxException e) { // Base exception for other API errors System.err.println("API error [" + e.getStatusCode() + "]: " + e.getMessage()); } ``` ### Error Properties | Property | Type | Description | | ----------------- | -------- | ---------------------------- | | `getMessage()` | `String` | Human-readable error message | | `getStatusCode()` | `int` | HTTP status code | | `getCode()` | `String` | Error code (if available) | --- ## Types ### VariableMimeType String values for variable content types: | Value | Description | | ------------ | ----------------------------- | | `"text"` | Plain text injection | | `"html"` | Rich HTML content | | `"image"` | Image URL or base64 content | | `"markdown"` | Markdown converted to text | ### DeliverableVariable Variable configuration for template injection: | Property | Type | Required | Description | | ------------------------ | ----------------------------- | -------- | ---------------------------------------------------- | | `placeholder` | `String` | Yes | Template placeholder (e.g., `{CompanyName}`) | | `text` | `String` | No\* | Value to inject | | `mimeType` | `String` | Yes | `"text"`, `"html"`, `"image"`, or `"markdown"` | | `isDisabled` | `Boolean` | No | Skip this variable during generation | | `subvariables` | `List` | No | Nested sub-variables for HTML content | | `variableStack` | `Object` | No | Multiple instances for repeating content | | `aiPrompt` | `String` | No | AI prompt for content generation (max 16,000 chars) | | `allowRichTextInjection` | `Boolean` | No | Allow rich text (HTML) to be injected for this variable | \*Required unless `variableStack` is provided or `isDisabled` is true. ### CreateDeliverableRequest Request configuration for `generateDeliverable`: | Property | Type | Required | Description | | -------------- | ----------------------------- | -------- | ------------------------------------------ | | `name` | `String` | Yes | Deliverable name (3-255 characters) | | `templateId` | `String` | Yes | Template ID to generate from | | `variables` | `List` | Yes | Variables for template substitution | | `description` | `String` | No | Description (up to 65,535 characters) | | `tags` | `List` | No | Tag strings to associate | ### UpdateDeliverableRequest Request configuration for `updateDeliverableInfo`: | Property | Type | Required | Description | | ------------- | -------------- | -------- | ---------------------------------------- | | `name` | `String` | No | Updated name (3-255 characters) | | `description` | `String` | No | Updated description | | `tags` | `List` | No | Replace all tags (empty list to remove) | ### ListDeliverablesRequest Options for `listDeliverables`: | Property | Type | Required | Description | | -------------- | ---------- | -------- | ------------------------------------ | | `limit` | `Integer` | No | Results per page (1-100, default 6) | | `offset` | `Integer` | No | Results to skip (default 0) | | `query` | `String` | No | Search query to filter by name | | `showTags` | `Boolean` | No | Include tags in the response | ### DeliverableRecord The deliverable object returned by both `listDeliverables` and `getDeliverableDetails`. Both methods return the same type. | Property | Type | Description | | -------------------- | ---------------------------- | ---------------------------------------- | | `id` | `String` | Unique deliverable ID (UUID) | | `name` | `String` | Deliverable name | | `description` | `String` | Description text | | `templateId` | `String` | Source template ID | | `templateName` | `String` | Source template name | | `templateNotDeleted` | `boolean` | Whether the source template still exists | | `createdBy` | `String` | User ID of the creator | | `email` | `String` | Creator's email address | | `fileSize` | `Long` | File size in bytes | | `fileType` | `String` | MIME type of the generated file | | `defaultFont` | `String` | Default font used | | `isActive` | `boolean` | Whether the deliverable is active | | `createdOn` | `String` | ISO 8601 creation timestamp | | `updatedOn` | `String` | ISO 8601 last update timestamp | | `variables` | `List` | Parsed variable objects with values | | `tags` | `List` | Associated tags (when `showTags=true`) | ### Tag Tag object included when `showTags` is enabled: | Property | Type | Description | | ----------- | --------- | ------------------------------------ | | `id` | `String` | Tag unique identifier (UUID) | | `label` | `String` | Tag display name | | `isActive` | `boolean` | Whether the tag is active | | `updatedOn` | `String` | ISO 8601 last update timestamp | | `createdOn` | `String` | ISO 8601 creation timestamp | | `createdBy` | `String` | User ID of the tag creator | | `orgId` | `String` | Organization ID | --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[TurboDocx Templating](/docs/TurboDocx%20Templating/How%20to%20Create%20a%20Template)** - How to create and configure document templates - **[Variable Reference](/docs/API/Deliverable%20API#variable-object-structure)** - Complete guide to variable types, formatting, and advanced injection options - **[API Reference](/docs/API/Deliverable%20API)** - Full REST API documentation for Deliverable endpoints --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) - [Maven Central](https://search.maven.org/artifact/com.turbodocx/turbodocx-sdk) - [API Reference](/docs/API/Deliverable%20API) --- # JavaScript / TypeScript SDK The official TurboDocx Deliverable SDK for JavaScript and TypeScript applications. Generate documents from templates with dynamic variable injection, download source files and PDFs, and manage deliverables programmatically. Available on npm as `@turbodocx/sdk`. ## Installation ```bash npm install @turbodocx/sdk ``` ```bash yarn add @turbodocx/sdk ``` ```bash pnpm add @turbodocx/sdk ``` ## Requirements - Node.js 18+ or modern browser - TypeScript 4.7+ (optional, for type checking) --- ## Configuration ```javascript const { Deliverable } = require("@turbodocx/sdk"); // Configure globally (recommended for server-side) Deliverable.configure({ apiKey: process.env.TURBODOCX_API_KEY, // Required: Your TurboDocx API key orgId: process.env.TURBODOCX_ORG_ID, // Required: Your organization ID // Optional: override base URL for testing // baseUrl: 'https://api.turbodocx.com' }); ``` ```typescript // Configure globally (recommended for server-side) Deliverable.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", // Required: Your TurboDocx API key orgId: process.env.TURBODOCX_ORG_ID || "", // Required: Your organization ID // Optional: override base URL for testing // baseUrl: 'https://api.turbodocx.com' }); ``` :::tip No Sender Email Required Unlike TurboSign, the Deliverable module only requires `apiKey` and `orgId` — no sender email or name is needed. ::: ### Environment Variables ```bash # .env TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here ``` --- ## Quick Start ### Generate a document from a template ```javascript const { Deliverable } = require("@turbodocx/sdk"); Deliverable.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, }); // Generate a document from a template with variables const result = await Deliverable.generateDeliverable({ name: "Q1 Report", templateId: "your-template-id", variables: [ { placeholder: "{CompanyName}", text: "Acme Corporation", mimeType: "text" }, { placeholder: "{Date}", text: "2026-03-12", mimeType: "text" }, ], description: "Quarterly business report", tags: ["reports", "quarterly"], }); console.log(JSON.stringify(result, null, 2)); ``` ```typescript Deliverable.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", orgId: process.env.TURBODOCX_ORG_ID || "", }); // Generate a document from a template with variables const request: CreateDeliverableRequest = { name: "Q1 Report", templateId: "your-template-id", variables: [ { placeholder: "{CompanyName}", text: "Acme Corporation", mimeType: "text" }, { placeholder: "{Date}", text: "2026-03-12", mimeType: "text" }, ], description: "Quarterly business report", tags: ["reports", "quarterly"], }; const result = await Deliverable.generateDeliverable(request); console.log(JSON.stringify(result, null, 2)); ``` ### Download and manage deliverables ```javascript const { Deliverable } = require("@turbodocx/sdk"); const { writeFileSync } = require("fs"); // List deliverables with pagination const list = await Deliverable.listDeliverables({ limit: 10, showTags: true }); console.log(`Total: ${list.totalRecords}`); // Get deliverable details const details = await Deliverable.getDeliverableDetails("deliverable-uuid"); console.log(`Name: ${details.name}`); // Download source file (DOCX/PPTX) const buffer = await Deliverable.downloadSourceFile("deliverable-uuid"); writeFileSync("report.docx", Buffer.from(buffer)); // Download PDF const pdfBuffer = await Deliverable.downloadPDF("deliverable-uuid"); writeFileSync("report.pdf", Buffer.from(pdfBuffer)); // Update deliverable await Deliverable.updateDeliverableInfo("deliverable-uuid", { name: "Q1 Report - Final", description: "Final quarterly business report", }); // Delete deliverable await Deliverable.deleteDeliverable("deliverable-uuid"); ``` ```typescript // List deliverables with pagination const list = await Deliverable.listDeliverables({ limit: 10, showTags: true }); console.log(`Total: ${list.totalRecords}`); // Get deliverable details const details = await Deliverable.getDeliverableDetails("deliverable-uuid"); console.log(`Name: ${details.name}`); // Download source file (DOCX/PPTX) const buffer = await Deliverable.downloadSourceFile("deliverable-uuid"); writeFileSync("report.docx", Buffer.from(buffer)); // Download PDF const pdfBuffer = await Deliverable.downloadPDF("deliverable-uuid"); writeFileSync("report.pdf", Buffer.from(pdfBuffer)); // Update deliverable await Deliverable.updateDeliverableInfo("deliverable-uuid", { name: "Q1 Report - Final", description: "Final quarterly business report", }); // Delete deliverable await Deliverable.deleteDeliverable("deliverable-uuid"); ``` --- ## Variable Types The Deliverable module supports four variable types for template injection: ### 1. Text Variables Inject plain text values into template placeholders: ```javascript const variables = [ { placeholder: "{CompanyName}", text: "Acme Corporation", mimeType: "text" }, { placeholder: "{Date}", text: "2026-03-12", mimeType: "text" }, ]; ``` ```typescript const variables: DeliverableVariable[] = [ { placeholder: "{CompanyName}", text: "Acme Corporation", mimeType: "text" }, { placeholder: "{Date}", text: "2026-03-12", mimeType: "text" }, ]; ``` ### 2. HTML Variables Inject rich HTML content with formatting: ```javascript const variables = [ { placeholder: "{Summary}", text: "This is a formatted summary with rich text.", mimeType: "html", }, ]; ``` ### 3. Image Variables Inject images by providing a URL or base64-encoded content: ```javascript const variables = [ { placeholder: "{Logo}", text: "https://example.com/logo.png", mimeType: "image", }, ]; ``` ### 4. Markdown Variables Inject markdown content that gets converted to formatted text: ```javascript const variables = [ { placeholder: "{Notes}", text: "## Key Points\n- First item\n- Second item\n\n**Important:** Review before submission.", mimeType: "markdown", }, ]; ``` :::info Variable Stack For repeating content (e.g., table rows), use `variableStack` instead of `text` to provide multiple values for the same placeholder. See the [Types section](#createdeliverablerequest) for details. ::: --- ## API Reference ### Configure Configure the SDK with your API credentials and organization settings. **Example:** ```javascript const { Deliverable } = require("@turbodocx/sdk"); Deliverable.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, // Optional: override for testing // baseUrl: 'https://api.turbodocx.com' }); ``` ```typescript Deliverable.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", orgId: process.env.TURBODOCX_ORG_ID || "", // Optional: override for testing // baseUrl: 'https://api.turbodocx.com' }); ``` :::caution API Credentials Required Both `apiKey` and `orgId` parameters are **required** for all API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: ### Generate deliverable Generate a new document from a template with variable substitution. ```javascript const result = await Deliverable.generateDeliverable({ name: "Q1 Report", templateId: "your-template-id", variables: [ { placeholder: "{CompanyName}", text: "Acme Corp", mimeType: "text" }, { placeholder: "{Date}", text: "2026-03-12", mimeType: "text" }, ], description: "Quarterly business report", tags: ["reports", "quarterly"], }); console.log(JSON.stringify(result, null, 2)); ``` ```typescript const result = await Deliverable.generateDeliverable({ name: "Q1 Report", templateId: "your-template-id", variables: [ { placeholder: "{CompanyName}", text: "Acme Corp", mimeType: "text" }, { placeholder: "{Date}", text: "2026-03-12", mimeType: "text" }, ], description: "Quarterly business report", tags: ["reports", "quarterly"], }); console.log(JSON.stringify(result, null, 2)); ``` ### List deliverables List deliverables with pagination, search, and filtering. ```javascript const list = await Deliverable.listDeliverables({ limit: 10, offset: 0, query: "report", showTags: true, }); console.log(JSON.stringify(list, null, 2)); ``` ```typescript const list = await Deliverable.listDeliverables({ limit: 10, offset: 0, query: "report", showTags: true, }); console.log(JSON.stringify(list, null, 2)); ``` ### Get deliverable details Retrieve the full details of a single deliverable, including variables and fonts. ```javascript const details = await Deliverable.getDeliverableDetails("deliverable-uuid", { showTags: true, }); console.log(JSON.stringify(details, null, 2)); ``` ```typescript const details = await Deliverable.getDeliverableDetails("deliverable-uuid", { showTags: true, }); console.log(JSON.stringify(details, null, 2)); ``` ### Update deliverable info Update a deliverable's name, description, or tags. ```javascript const result = await Deliverable.updateDeliverableInfo("deliverable-uuid", { name: "Q1 Report - Final", description: "Final quarterly business report", tags: ["reports", "final"], }); console.log(JSON.stringify(result, null, 2)); ``` ```typescript const result = await Deliverable.updateDeliverableInfo("deliverable-uuid", { name: "Q1 Report - Final", description: "Final quarterly business report", tags: ["reports", "final"], }); console.log(JSON.stringify(result, null, 2)); ``` ### Delete deliverable Soft-delete a deliverable. ```javascript const result = await Deliverable.deleteDeliverable("deliverable-uuid"); console.log(JSON.stringify(result, null, 2)); ``` ```typescript const result = await Deliverable.deleteDeliverable("deliverable-uuid"); console.log(JSON.stringify(result, null, 2)); ``` ### Download source file Download the original source file (DOCX or PPTX). ```javascript const buffer = await Deliverable.downloadSourceFile("deliverable-uuid"); // Node.js: Save to file const { writeFileSync } = require("fs"); writeFileSync("report.docx", Buffer.from(buffer)); ``` ```typescript const buffer = await Deliverable.downloadSourceFile("deliverable-uuid"); // Node.js: Save to file writeFileSync("report.docx", Buffer.from(buffer)); ``` ### Download PDF Download the PDF version of a deliverable. ```javascript const buffer = await Deliverable.downloadPDF("deliverable-uuid"); // Node.js: Save to file const { writeFileSync } = require("fs"); writeFileSync("report.pdf", Buffer.from(buffer)); ``` ```typescript const buffer = await Deliverable.downloadPDF("deliverable-uuid"); // Node.js: Save to file writeFileSync("report.pdf", Buffer.from(buffer)); ``` --- ## Error Handling The SDK provides typed error classes for different failure scenarios. All errors extend the base `TurboDocxError` class. ### Error Classes | Error Class | Status Code | Code | Description | | --------------------- | ----------- | ---------------------- | ---------------------------------------- | | `TurboDocxError` | varies | varies | Base error class for all SDK errors | | `AuthenticationError` | 401 | `AUTHENTICATION_ERROR` | Invalid or missing API credentials | | `AuthorizationError` | 403 | `AUTHORIZATION_ERROR` | Forbidden: API key lacks required permissions | | `ValidationError` | 400 | `VALIDATION_ERROR` | Invalid request parameters | | `NotFoundError` | 404 | `NOT_FOUND` | Deliverable or template not found | | `ConflictError` | 409 | `CONFLICT` | Resource conflict | | `RateLimitError` | 429 | `RATE_LIMIT_EXCEEDED` | Too many requests | | `NetworkError` | - | `NETWORK_ERROR` | Network connectivity issues | ### Handling Errors ```javascript const { Deliverable, TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, } = require("@turbodocx/sdk"); try { const result = await Deliverable.generateDeliverable({ name: "Q1 Report", templateId: "your-template-id", variables: [ { placeholder: "{CompanyName}", text: "Acme Corp", mimeType: "text" }, ], }); } catch (error) { if (error instanceof AuthenticationError) { console.error("Authentication failed:", error.message); // Check your API key and org ID } else if (error instanceof ValidationError) { console.error("Validation error:", error.message); // Check request parameters } else if (error instanceof NotFoundError) { console.error("Resource not found:", error.message); // Template or deliverable doesn't exist } else if (error instanceof RateLimitError) { console.error("Rate limited:", error.message); // Wait and retry } else if (error instanceof NetworkError) { console.error("Network error:", error.message); // Check connectivity } else if (error instanceof TurboDocxError) { console.error("SDK error:", error.message, error.statusCode, error.code); } } ``` ```typescript Deliverable, TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, } from "@turbodocx/sdk"; try { const result = await Deliverable.generateDeliverable({ name: "Q1 Report", templateId: "your-template-id", variables: [ { placeholder: "{CompanyName}", text: "Acme Corp", mimeType: "text" }, ], }); } catch (error) { if (error instanceof AuthenticationError) { console.error("Authentication failed:", error.message); // Check your API key and org ID } else if (error instanceof ValidationError) { console.error("Validation error:", error.message); // Check request parameters } else if (error instanceof NotFoundError) { console.error("Resource not found:", error.message); // Template or deliverable doesn't exist } else if (error instanceof RateLimitError) { console.error("Rate limited:", error.message); // Wait and retry } else if (error instanceof NetworkError) { console.error("Network error:", error.message); // Check connectivity } else if (error instanceof TurboDocxError) { console.error("SDK error:", error.message, error.statusCode, error.code); } } ``` ### Error Properties All errors include these properties: | Property | Type | Description | | ------------ | --------------------- | -------------------------------- | | `message` | `string` | Human-readable error description | | `statusCode` | `number \| undefined` | HTTP status code (if applicable) | | `code` | `string \| undefined` | Machine-readable error code | --- ## TypeScript Types The SDK exports TypeScript types for full type safety. Import them directly from the package. ### Importing Types ```typescript // Variable types DeliverableVariable, VariableMimeType, // Request types DeliverableConfig, CreateDeliverableRequest, UpdateDeliverableRequest, ListDeliverablesOptions, // Response types CreateDeliverableResponse, UpdateDeliverableResponse, DeleteDeliverableResponse, DeliverableListResponse, // Record types DeliverableRecord, Tag, Font, } from "@turbodocx/sdk"; ``` ### VariableMimeType Union type for variable content types: ```typescript type VariableMimeType = "text" | "html" | "image" | "markdown"; ``` ### DeliverableVariable Variable configuration for template injection: | Property | Type | Required | Description | | ------------------------ | ----------------------- | -------- | ---------------------------------------------------- | | `placeholder` | `string` | Yes | Template placeholder (e.g., `{CompanyName}`) | | `text` | `string` | No\* | Value to inject | | `mimeType` | `VariableMimeType` | Yes | `"text"`, `"html"`, `"image"`, or `"markdown"` | | `isDisabled` | `boolean` | No | Skip this variable during generation | | `subvariables` | `DeliverableVariable[]` | No | Nested sub-variables for HTML content | | `variableStack` | `object \| array` | No | Multiple instances for repeating content | | `aiPrompt` | `string` | No | AI prompt for content generation (max 16,000 chars) | | `allowRichTextInjection` | `boolean` | No | Whether to allow rich text injection | \*Required unless `variableStack` is provided or `isDisabled` is true. ### CreateDeliverableRequest Request configuration for `generateDeliverable`: | Property | Type | Required | Description | | -------------- | ----------------------- | -------- | ------------------------------------------ | | `name` | `string` | Yes | Deliverable name (3-255 characters) | | `templateId` | `string` | Yes | Template ID to generate from | | `variables` | `DeliverableVariable[]` | Yes | Variables for template substitution | | `description` | `string` | No | Description (up to 65,535 characters) | | `tags` | `string[]` | No | Tag strings to associate | ### UpdateDeliverableRequest Request configuration for `updateDeliverableInfo`: | Property | Type | Required | Description | | ------------- | ---------- | -------- | ---------------------------------------- | | `name` | `string` | No | Updated name (3-255 characters) | | `description` | `string` | No | Updated description | | `tags` | `string[]` | No | Replace all tags (empty array to remove) | ### ListDeliverablesOptions Options for `listDeliverables`:   | Property | Type | Required | Description | | -------------- | ---------- | -------- | ------------------------------------ | | `limit` | `number` | No | Results per page (1-100, default 6) | | `offset` | `number` | No | Results to skip (default 0) | | `query` | `string` | No | Search query to filter by name | | `showTags` | `boolean` | No | Include tags in the response | ### DeliverableRecord The deliverable object returned by both `listDeliverables` and `getDeliverableDetails`. Fields marked _details only_ are populated only by `getDeliverableDetails`:   | Property | Type | Description | | -------------------- | ----------------------- | ---------------------------------------------------- | | `id` | `string` | Unique deliverable ID (UUID) | | `name` | `string` | Deliverable name | | `description` | `string` | Description text | | `templateId` | `string` | Source template ID | | `templateName` | `string` | Source template name | | `templateNotDeleted` | `boolean` | Whether the source template still exists | | `createdBy` | `string` | User ID of the creator | | `email` | `string` | Creator's email address | | `fileSize` | `number` | File size in bytes | | `fileType` | `string` | MIME type of the generated file | | `defaultFont` | `string` | Default font used | | `fonts` | `Font[]` | Fonts used in the document | | `isActive` | `boolean` | Whether the deliverable is active | | `createdOn` | `string` | ISO 8601 creation timestamp | | `updatedOn` | `string` | ISO 8601 last update timestamp | | `variables` | `DeliverableVariable[]` | Parsed variable objects with values (_details only_) | | `tags` | `Tag[]` | Associated tags (when `showTags=true`) | ### Tag Tag object included when `showTags` is enabled:   | Property | Type | Description | | ----------- | --------- | ------------------------------------ | | `id` | `string` | Tag unique identifier (UUID) | | `label` | `string` | Tag display name | | `isActive` | `boolean` | Whether the tag is active | | `updatedOn` | `string` | ISO 8601 last update timestamp | | `createdOn` | `string` | ISO 8601 creation timestamp | | `createdBy` | `string` | User ID of the tag creator | | `orgId` | `string` | Organization ID | --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[TurboDocx Templating](/docs/TurboDocx%20Templating/How%20to%20Create%20a%20Template)** - How to create and configure document templates - **[Variable Reference](/docs/API/Deliverable%20API#variable-object-structure)** - Complete guide to variable types, formatting, and advanced injection options - **[API Reference](/docs/API/Deliverable%20API)** - Full REST API documentation for Deliverable endpoints --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) - [npm Package](https://www.npmjs.com/package/@turbodocx/sdk) - [API Reference](/docs/API/Deliverable%20API) --- # PHP SDK The official TurboDocx Deliverable SDK for PHP applications. Generate documents from templates with dynamic variable injection, download source files and PDFs, and manage deliverables programmatically. Available on Packagist as `turbodocx/sdk`. ## Installation ```bash composer require turbodocx/sdk ``` ## Requirements - PHP 8.1 or higher - Composer - ext-json - ext-fileinfo :::tip Modern PHP Features This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience. ::: --- ## Configuration ```php ```php :::tip No senderEmail Required Unlike TurboSign, the Deliverable module only requires `apiKey` and `orgId` — no sender email or name is needed. ::: ### Environment Variables ```bash # .env TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here ``` :::caution API Credentials Required An `apiKey` (or `accessToken` as an alternative) is **required** for all API requests; `orgId` is optional but recommended for organization-scoped operations. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Generate a document from a template ```php 'Q1 Report', 'templateId' => 'your-template-id', 'variables' => [ ['placeholder' => '{CompanyName}', 'text' => 'Acme Corporation', 'mimeType' => 'text'], ['placeholder' => '{Date}', 'text' => '2026-03-12', 'mimeType' => 'text'], ], 'description' => 'Quarterly business report', 'tags' => ['reports', 'quarterly'], ]); echo "Deliverable ID: {$result['results']['deliverable']['id']}\n"; ``` :::caution Always Handle Errors The above examples omit error handling for brevity. In production, wrap all Deliverable calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: ### Download and manage deliverables ```php 10, 'showTags' => true]); echo "Total: {$list['totalRecords']}\n"; // Get deliverable details $details = Deliverable::getDeliverableDetails('deliverable-uuid', showTags: true); echo "Name: {$details['name']}\n"; // Download source file (DOCX/PPTX) $sourceFile = Deliverable::downloadSourceFile('deliverable-uuid'); file_put_contents('report.docx', $sourceFile); // Download PDF $pdfFile = Deliverable::downloadPDF('deliverable-uuid'); file_put_contents('report.pdf', $pdfFile); // Update deliverable $updated = Deliverable::updateDeliverableInfo('deliverable-uuid', [ 'name' => 'Q1 Report - Final', 'description' => 'Final quarterly business report', ]); // Delete deliverable $deleted = Deliverable::deleteDeliverable('deliverable-uuid'); ``` :::caution Always Handle Errors The above examples omit error handling for brevity. In production, wrap all Deliverable calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: --- ## Variable Types The Deliverable module supports four variable types for template injection: ### 1. Text Variables Inject plain text values into template placeholders: ```php $variables = [ ['placeholder' => '{CompanyName}', 'text' => 'Acme Corporation', 'mimeType' => 'text'], ['placeholder' => '{Date}', 'text' => '2026-03-12', 'mimeType' => 'text'], ]; ``` ### 2. HTML Variables Inject rich HTML content with formatting: ```php $variables = [ [ 'placeholder' => '{Summary}', 'text' => 'This is a formatted summary with rich text.', 'mimeType' => 'html', ], ]; ``` ### 3. Image Variables Inject images by providing a URL or base64-encoded content: ```php $variables = [ [ 'placeholder' => '{Logo}', 'text' => 'https://example.com/logo.png', 'mimeType' => 'image', ], ]; ``` ### 4. Markdown Variables Inject markdown content that gets converted to formatted text: ```php $variables = [ [ 'placeholder' => '{Notes}', 'text' => "## Key Points\n- First item\n- Second item\n\n**Important:** Review before submission.", 'mimeType' => 'markdown', ], ]; ``` :::info Variable Stack For repeating content (e.g., table rows), use `variableStack` instead of `text` to provide multiple values for the same placeholder. See the [PHP Types section](#php-types) for details. ::: --- ## API Reference ### Configure Configure the SDK with your API credentials and organization settings. ```php use TurboDocx\Deliverable; use TurboDocx\Config\DeliverableConfig; // Manual configuration Deliverable::configure(new DeliverableConfig( apiKey: 'your-api-key', orgId: 'your-org-id' )); // Or from environment Deliverable::configure(DeliverableConfig::fromEnvironment()); ``` ### Generate deliverable Generate a new document from a template with variable substitution. ```php $result = Deliverable::generateDeliverable([ 'name' => 'Q1 Report', 'templateId' => 'your-template-id', 'variables' => [ ['placeholder' => '{CompanyName}', 'text' => 'Acme Corp', 'mimeType' => 'text'], ['placeholder' => '{Date}', 'text' => '2026-03-12', 'mimeType' => 'text'], ], 'description' => 'Quarterly business report', 'tags' => ['reports', 'quarterly'], ]); echo "Deliverable ID: {$result['results']['deliverable']['id']}\n"; ``` ### List deliverables List deliverables with pagination, search, and filtering. ```php $list = Deliverable::listDeliverables([ 'limit' => 10, 'offset' => 0, 'query' => 'report', 'showTags' => true, ]); echo "Total records: {$list['totalRecords']}\n"; foreach ($list['results'] as $deliverable) { echo " {$deliverable['name']}\n"; } ``` ### Get deliverable details Retrieve the full details of a single deliverable, including variables and fonts. ```php $details = Deliverable::getDeliverableDetails('deliverable-uuid', showTags: true); echo "Name: {$details['name']}\n"; echo "Template: {$details['templateName']}\n"; echo "Created: {$details['createdOn']}\n"; ``` ### Update deliverable info Update a deliverable's name, description, or tags. ```php $result = Deliverable::updateDeliverableInfo('deliverable-uuid', [ 'name' => 'Q1 Report - Final', 'description' => 'Final quarterly business report', 'tags' => ['reports', 'final'], ]); echo "Updated: {$result['deliverableId']}\n"; ``` ### Delete deliverable Soft-delete a deliverable. ```php $result = Deliverable::deleteDeliverable('deliverable-uuid'); echo "Deleted: {$result['deliverableId']}\n"; echo "Message: {$result['message']}\n"; ``` ### Download source file Download the original source file (DOCX or PPTX). ```php $sourceFile = Deliverable::downloadSourceFile('deliverable-uuid'); // Save to file file_put_contents('report.docx', $sourceFile); // Or send as HTTP response header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); header('Content-Disposition: attachment; filename="report.docx"'); echo $sourceFile; ``` ### Download PDF Download the PDF version of a deliverable. ```php $pdfFile = Deliverable::downloadPDF('deliverable-uuid'); // Save to file file_put_contents('report.pdf', $pdfFile); // Or send as HTTP response header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="report.pdf"'); echo $pdfFile; ``` --- ## Error Handling The SDK provides typed exceptions for different error scenarios. ### Error Classes | Error Class | Status Code | Description | | ------------------------- | ----------- | ---------------------------------- | | `TurboDocxException` | varies | Base exception for all SDK errors | | `AuthenticationException` | 401 | Invalid or missing API credentials | | `AuthorizationException` | 403 | API key lacks required permissions | | `ValidationException` | 400 | Invalid request parameters | | `NotFoundException` | 404 | Deliverable or template not found | | `ConflictException` | 409 | Resource conflict | | `RateLimitException` | 429 | Too many requests | | `NetworkException` | - | Network connectivity issues | ### Handling Errors ```php 'Q1 Report', 'templateId' => 'your-template-id', 'variables' => [ ['placeholder' => '{CompanyName}', 'text' => 'Acme Corp', 'mimeType' => 'text'], ], ]); } catch (AuthenticationException $e) { // 401 - Invalid API key or access token echo "Authentication failed: {$e->getMessage()}\n"; } catch (AuthorizationException $e) { // 403 - API key lacks required permissions echo "Authorization error: {$e->getMessage()}\n"; } catch (ValidationException $e) { // 400 - Invalid request data echo "Validation error: {$e->getMessage()}\n"; } catch (NotFoundException $e) { // 404 - Deliverable or template not found echo "Not found: {$e->getMessage()}\n"; } catch (ConflictException $e) { // 409 - Resource conflict echo "Conflict: {$e->getMessage()}\n"; } catch (RateLimitException $e) { // 429 - Rate limit exceeded echo "Rate limit: {$e->getMessage()}\n"; } catch (NetworkException $e) { // Network/connection error echo "Network error: {$e->getMessage()}\n"; } ``` ### Error Properties All exceptions extend `TurboDocxException` and include: - `getMessage()` - Human-readable error message - `statusCode` - HTTP status code (if applicable) - `errorCode` - Error code string (e.g., 'AUTHENTICATION_ERROR') --- ## PHP Types ### Variable Array Structure Variables are passed as associative arrays with the following keys: | Key | Type | Required | Description | | ------------------------ | -------------- | -------- | ---------------------------------------------------- | | `placeholder` | `string` | Yes | Template placeholder (e.g., `{CompanyName}`) | | `text` | `string` | No\* | Value to inject | | `mimeType` | `string` | Yes | `"text"`, `"html"`, `"image"`, or `"markdown"` | | `isDisabled` | `bool` | No | Skip this variable during generation | | `allowRichTextInjection` | `bool` | No | Whether to allow rich text injection | | `subvariables` | `array` | No | Nested sub-variables for HTML content | | `variableStack` | `array` | No | Multiple instances for repeating content | | `aiPrompt` | `string` | No | AI prompt for content generation (max 16,000 chars) | \*Required unless `variableStack` is provided or `isDisabled` is true. ### Generate Deliverable Request Request array for `generateDeliverable`: | Key | Type | Required | Description | | -------------- | -------- | -------- | ------------------------------------------ | | `name` | `string` | Yes | Deliverable name (3-255 characters) | | `templateId` | `string` | Yes | Template ID to generate from | | `variables` | `array` | Yes | Variables for template substitution | | `description` | `string` | No | Description (up to 65,535 characters) | | `tags` | `array` | No | Tag strings to associate | ### Update Deliverable Request Request array for `updateDeliverableInfo`: | Key | Type | Required | Description | | ------------- | -------- | -------- | ---------------------------------------- | | `name` | `string` | No | Updated name (3-255 characters) | | `description` | `string` | No | Updated description | | `tags` | `array` | No | Replace all tags (empty array to remove) | ### List Deliverables Options Options array for `listDeliverables`: | Key | Type | Required | Description | | -------------- | -------- | -------- | ------------------------------------ | | `limit` | `int` | No | Results per page (1-100, default 6) | | `offset` | `int` | No | Results to skip (default 0) | | `query` | `string` | No | Search query to filter by name | | `showTags` | `bool` | No | Include tags in the response | ### Deliverable Record The deliverable record returned by `listDeliverables`: | Key | Type | Description | | ---------------- | -------- | ------------------------------------- | | `id` | `string` | Unique deliverable ID (UUID) | | `name` | `string` | Deliverable name | | `description` | `string` | Description text | | `templateId` | `string` | Source template ID | | `createdBy` | `string` | User ID of the creator | | `email` | `string` | Creator's email address | | `fileSize` | `int` | File size in bytes | | `fileType` | `string` | MIME type of the generated file | | `defaultFont` | `string` | Default font used | | `fonts` | `array` | Fonts used in the document | | `isActive` | `bool` | Whether the deliverable is active | | `createdOn` | `string` | ISO 8601 creation timestamp | | `updatedOn` | `string` | ISO 8601 last update timestamp | | `tags` | `array` | Associated tags (when `showTags=true`)| ### Deliverable Detail Record The deliverable record returned by `getDeliverableDetails`. Includes all fields from [Deliverable Record](#deliverable-record) **except `fileSize`**, plus: | Key | Type | Description | | -------------------- | -------- | ---------------------------------------- | | `templateName` | `string` | Source template name | | `templateNotDeleted` | `bool` | Whether the source template still exists | | `variables` | `array` | Parsed variable objects with values | ### Tag Tag object included when `showTags` is enabled. Each tag is an associative array with: | Key | Type | Description | | ----------- | -------- | ------------------------------------ | | `id` | `string` | Tag unique identifier (UUID) | | `label` | `string` | Tag display name | | `isActive` | `bool` | Whether the tag is active | | `updatedOn` | `string` | ISO 8601 last update timestamp | | `createdOn` | `string` | ISO 8601 creation timestamp | | `createdBy` | `string` | User ID of the tag creator | | `orgId` | `string` | Organization ID | --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[TurboDocx Templating](/docs/TurboDocx%20Templating/How%20to%20Create%20a%20Template)** - How to create and configure document templates - **[Variable Reference](/docs/API/Deliverable%20API#variable-object-structure)** - Complete guide to variable types, formatting, and advanced injection options - **[API Reference](/docs/API/Deliverable%20API)** - Full REST API documentation for Deliverable endpoints --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) - [Packagist Package](https://packagist.org/packages/turbodocx/sdk) - [API Reference](/docs/API/Deliverable%20API) --- # Python SDK The official TurboDocx Deliverable SDK for Python applications. Generate documents from templates with dynamic variable injection, download source files and PDFs, and manage deliverables programmatically with async/await patterns and comprehensive error handling. Available on PyPI as `turbodocx-sdk`. ## Installation ```bash pip install turbodocx-sdk ``` ```bash poetry add turbodocx-sdk ``` ```bash pipenv install turbodocx-sdk ``` ## Requirements - Python 3.8+ - `httpx` (installed automatically) - `pydantic` (installed automatically) --- ## Configuration ```python from turbodocx_sdk import Deliverable # Configure globally (recommended) Deliverable.configure( api_key=os.environ["TURBODOCX_API_KEY"], # Required: Your TurboDocx API key org_id=os.environ["TURBODOCX_ORG_ID"], # Required: Your organization ID # base_url="https://api.turbodocx.com" # Optional: Override base URL ) ``` :::tip No Sender Email Required Unlike TurboSign, the Deliverable module only requires `api_key` and `org_id` — no sender email or name is needed. ::: ### Environment Variables ```bash # .env TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here ``` :::caution API Credentials Required Both `api_key` and `org_id` parameters are **required** for all API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Generate a document from a template ```python from turbodocx_sdk import Deliverable Deliverable.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"] ) async def generate_report(): # Generate a document from a template with variables result = await Deliverable.generate_deliverable( name="Q1 Report", template_id="your-template-id", variables=[ {"placeholder": "{CompanyName}", "text": "Acme Corporation", "mimeType": "text"}, {"placeholder": "{Date}", "text": "2026-03-12", "mimeType": "text"}, ], description="Quarterly business report", tags=["reports", "quarterly"], ) print("Result:", json.dumps(result, indent=2)) asyncio.run(generate_report()) ``` ### Download and manage deliverables ```python from turbodocx_sdk import Deliverable Deliverable.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"] ) async def manage_deliverables(): # List deliverables with pagination items = await Deliverable.list_deliverables(limit=10, show_tags=True) print(f"Total: {items['totalRecords']}") # Get deliverable details details = await Deliverable.get_deliverable_details("deliverable-uuid") print(f"Name: {details['name']}") # Download source file (DOCX/PPTX) source_bytes = await Deliverable.download_source_file("deliverable-uuid") with open("report.docx", "wb") as f: f.write(source_bytes) # Download PDF pdf_bytes = await Deliverable.download_pdf("deliverable-uuid") with open("report.pdf", "wb") as f: f.write(pdf_bytes) # Update deliverable await Deliverable.update_deliverable_info( "deliverable-uuid", name="Q1 Report - Final", description="Final quarterly business report", ) # Delete deliverable await Deliverable.delete_deliverable("deliverable-uuid") asyncio.run(manage_deliverables()) ``` --- ## Variable Types The Deliverable module supports four variable types for template injection: ### 1. Text Variables Inject plain text values into template placeholders: ```python variables = [ {"placeholder": "{CompanyName}", "text": "Acme Corporation", "mimeType": "text"}, {"placeholder": "{Date}", "text": "2026-03-12", "mimeType": "text"}, ] ``` ### 2. HTML Variables Inject rich HTML content with formatting: ```python variables = [ { "placeholder": "{Summary}", "text": "This is a formatted summary with rich text.", "mimeType": "html", }, ] ``` ### 3. Image Variables Inject images by providing a URL or base64-encoded content: ```python variables = [ { "placeholder": "{Logo}", "text": "https://example.com/logo.png", "mimeType": "image", }, ] ``` ### 4. Markdown Variables Inject markdown content that gets converted to formatted text: ```python variables = [ { "placeholder": "{Notes}", "text": "## Key Points\n- First item\n- Second item\n\n**Important:** Review before submission.", "mimeType": "markdown", }, ] ``` :::info Variable Stack For repeating content (e.g., table rows), use `variableStack` instead of `text` to provide multiple values for the same placeholder. See the [Types section](#createdeliverablerequest) for details. ::: --- ## API Reference :::note Async snippets The snippets below are partial and run inside an `async` function. They assume you have already called `Deliverable.configure(...)` and that `asyncio` and `json` are imported (`import asyncio, json`). For a complete runnable script, wrap the calls in `async def main(): ...` and run with `asyncio.run(main())`, as shown in [Quick Start](#quick-start). ::: ### Configure Configure the SDK with your API credentials and organization settings. ```python Deliverable.configure( api_key: Optional[str] = None, # API key (or use access_token) access_token: Optional[str] = None, # OAuth2 access token (alternative to api_key) base_url: str = "https://api.turbodocx.com", # Optional: API base URL org_id: Optional[str] = None, # Required: Your organization ID ) ``` :::caution API Credentials Required All parameters are optional and keyword-only. Either `api_key` or `access_token` must be provided for authentication, and `org_id` is **required** for all Deliverable operations (enforced at runtime). To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: ### Generate deliverable Generate a new document from a template with variable substitution. ```python result = await Deliverable.generate_deliverable( name="Q1 Report", template_id="your-template-id", variables=[ {"placeholder": "{CompanyName}", "text": "Acme Corp", "mimeType": "text"}, {"placeholder": "{Date}", "text": "2026-03-12", "mimeType": "text"}, ], description="Quarterly business report", tags=["reports", "quarterly"], ) print("Result:", json.dumps(result, indent=2)) ``` ### List deliverables List deliverables with pagination, search, and filtering. ```python items = await Deliverable.list_deliverables( limit=10, offset=0, query="report", show_tags=True, ) print("Result:", json.dumps(items, indent=2)) ``` ### Get deliverable details Retrieve the full details of a single deliverable, including variables and fonts. ```python details = await Deliverable.get_deliverable_details("deliverable-uuid", show_tags=True) print("Result:", json.dumps(details, indent=2)) ``` ### Update deliverable info Update a deliverable's name, description, or tags. ```python result = await Deliverable.update_deliverable_info( "deliverable-uuid", name="Q1 Report - Final", description="Final quarterly business report", tags=["reports", "final"], ) print("Result:", json.dumps(result, indent=2)) ``` ### Delete deliverable Soft-delete a deliverable. ```python result = await Deliverable.delete_deliverable("deliverable-uuid") print("Result:", json.dumps(result, indent=2)) ``` ### Download source file Download the original source file (DOCX or PPTX). ```python source_bytes = await Deliverable.download_source_file("deliverable-uuid") # Save to file with open("report.docx", "wb") as f: f.write(source_bytes) ``` ### Download PDF Download the PDF version of a deliverable. ```python pdf_bytes = await Deliverable.download_pdf("deliverable-uuid") # Save to file with open("report.pdf", "wb") as f: f.write(pdf_bytes) ``` --- ## Error Handling The SDK provides typed error classes for different failure scenarios. All errors extend the base `TurboDocxError` class. ### Error Classes | Error Class | Status Code | Description | | --------------------- | ----------- | ----------------------------------- | | `TurboDocxError` | varies | Base error class for all SDK errors | | `AuthenticationError` | 401 | Invalid or missing API credentials | | `AuthorizationError` | 403 | Authenticated but lacks required permissions | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Deliverable or template not found | | `ConflictError` | 409 | Request conflicts with current resource state | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | ### Handling Errors ```python from turbodocx_sdk import ( Deliverable, TurboDocxError, AuthenticationError, AuthorizationError, ValidationError, NotFoundError, ConflictError, RateLimitError, NetworkError, ) async def main(): try: result = await Deliverable.generate_deliverable( name="Q1 Report", template_id="your-template-id", variables=[ {"placeholder": "{CompanyName}", "text": "Acme Corp", "mimeType": "text"}, ], ) except AuthenticationError as e: print(f"Authentication failed: {e}") # Check your API key and org ID except AuthorizationError as e: print(f"Not authorized: {e}") # Authenticated, but lacks permission for this operation except ValidationError as e: print(f"Validation error: {e}") # Check request parameters except NotFoundError as e: print(f"Resource not found: {e}") # Template or deliverable doesn't exist except ConflictError as e: print(f"Conflict: {e}") # Request conflicts with the current resource state except RateLimitError as e: print(f"Rate limited: {e}") # Wait and retry except NetworkError as e: print(f"Network error: {e}") # Check connectivity except TurboDocxError as e: print(f"SDK error: {e}, status_code={e.status_code}, code={e.code}") asyncio.run(main()) ``` ### Error Properties All errors include these properties: | Property | Type | Description | | ------------- | ------------- | --------------------------------------------------- | | `message` | `str` | Human-readable error description (via `str(error)`) | | `status_code` | `int \| None` | HTTP status code (if applicable) | | `code` | `str \| None` | Machine-readable error code | --- ## Python Types The SDK uses Python type hints with `Dict[str, Any]` for flexible JSON-like structures. ### Importing Types ```python from typing import Dict, List, Any, Optional ``` ### DeliverableVariable Variable configuration for template injection:   | Property | Type | Required | Description | | ------------------------ | ----------------- | -------- | ---------------------------------------------------- | | `placeholder` | `str` | Yes | Template placeholder (e.g., `{CompanyName}`) | | `text` | `str` | No\* | Value to inject | | `mimeType` | `str` | Yes | `"text"`, `"html"`, `"image"`, or `"markdown"` | | `isDisabled` | `bool` | No | Skip this variable during generation | | `subvariables` | `list[dict]` | No | Nested sub-variables for HTML content | | `variableStack` | `dict \| list` | No | Multiple instances for repeating content | | `aiPrompt` | `str` | No | AI prompt for content generation (max 16,000 chars) | \*Required unless `variableStack` is provided or `isDisabled` is true. ### CreateDeliverableRequest Request configuration for `generate_deliverable`:   | Property | Type | Required | Description | | --------------- | ------------ | -------- | ------------------------------------------ | | `name` | `str` | Yes | Deliverable name (3-255 characters) | | `template_id` | `str` | Yes | Template ID to generate from | | `variables` | `list[dict]` | Yes | Variables for template substitution | | `description` | `str` | No | Description (up to 65,535 characters) | | `tags` | `list[str]` | No | Tag strings to associate | ### UpdateDeliverableRequest Request configuration for `update_deliverable_info`:   | Property | Type | Required | Description | | ------------- | ----------- | -------- | ---------------------------------------- | | `name` | `str` | No | Updated name (3-255 characters) | | `description` | `str` | No | Updated description | | `tags` | `list[str]` | No | Replace all tags (empty list to remove) | ### ListDeliverablesOptions Options for `list_deliverables`:   | Property | Type | Required | Description | | --------------- | ------- | -------- | ------------------------------------ | | `limit` | `int` | No | Results per page (1-100, default 6) | | `offset` | `int` | No | Results to skip (default 0) | | `query` | `str` | No | Search query to filter by name | | `show_tags` | `bool` | No | Include tags in the response | ### DeliverableRecord The deliverable object returned by `list_deliverables`:   | Property | Type | Description | | ----------------- | -------- | ------------------------------------- | | `id` | `str` | Unique deliverable ID (UUID) | | `name` | `str` | Deliverable name | | `description` | `str` | Description text | | `templateId` | `str` | Source template ID | | `createdBy` | `str` | User ID of the creator | | `email` | `str` | Creator's email address | | `fileSize` | `int` | File size in bytes | | `fileType` | `str` | MIME type of the generated file | | `defaultFont` | `str` | Default font used | | `fonts` | `list` | Fonts used in the document | | `isActive` | `bool` | Whether the deliverable is active | | `createdOn` | `str` | ISO 8601 creation timestamp | | `updatedOn` | `str` | ISO 8601 last update timestamp | | `tags` | `list` | Associated tags (when `show_tags=True`)| ### DeliverableDetailRecord The deliverable object returned by `get_deliverable_details`. Includes all fields from [DeliverableRecord](#deliverablerecord) **except `fileSize`**, plus:   | Property | Type | Description | | -------------------- | ------------ | ---------------------------------------- | | `templateName` | `str` | Source template name | | `templateNotDeleted` | `bool` | Whether the source template still exists | | `variables` | `list[dict]` | Parsed variable objects with values | ### Tag Tag object included when `show_tags` is enabled. Each tag is a `dict` with: | Key | Type | Description | | ----------- | ------ | ------------------------------------ | | `id` | `str` | Tag unique identifier (UUID) | | `label` | `str` | Tag display name | | `isActive` | `bool` | Whether the tag is active | | `updatedOn` | `str` | ISO 8601 last update timestamp | | `createdOn` | `str` | ISO 8601 creation timestamp | | `createdBy` | `str` | User ID of the tag creator | | `orgId` | `str` | Organization ID | --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[TurboDocx Templating](/docs/TurboDocx%20Templating/How%20to%20Create%20a%20Template)** - How to create and configure document templates - **[Variable Reference](/docs/API/Deliverable%20API#variable-object-structure)** - Complete guide to variable types, formatting, and advanced injection options - **[API Reference](/docs/API/Deliverable%20API)** - Full REST API documentation for Deliverable endpoints --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) - [PyPI Package](https://pypi.org/project/turbodocx-sdk/) - [API Reference](/docs/API/Deliverable%20API) --- # Go SDK The official TurboDocx SDK for Go applications. Build document generation and digital signature workflows with idiomatic Go patterns, context support, and comprehensive error handling. Available as `github.com/TurboDocx/SDK/packages/go-sdk`. ## Installation ```bash go get github.com/TurboDocx/SDK/packages/go-sdk ``` ## Requirements - Go 1.21+ --- ## Configuration ```go package main "log" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { // Create a new client (reads SenderEmail from TURBODOCX_SENDER_EMAIL) client, err := turbodocx.NewClient( os.Getenv("TURBODOCX_API_KEY"), os.Getenv("TURBODOCX_ORG_ID"), ) if err != nil { log.Fatal(err) } _ = client // Or with custom configuration client, err = turbodocx.NewClientWithConfig(turbodocx.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), SenderEmail: os.Getenv("TURBODOCX_SENDER_EMAIL"), // Required for TurboSign BaseURL: "https://api.turbodocx.com", // Optional custom base URL }) if err != nil { log.Fatal(err) } _ = client } ``` ### Environment Variables ```bash export TURBODOCX_API_KEY=your_api_key_here export TURBODOCX_ORG_ID=your_org_id_here export TURBODOCX_SENDER_EMAIL=you@example.com # Required for TurboSign (reply-to address) ``` --- ## Quick Start ### Send a Document for Signature ```go package main "context" "encoding/json" "fmt" "log" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { client, err := turbodocx.NewClient( os.Getenv("TURBODOCX_API_KEY"), os.Getenv("TURBODOCX_ORG_ID"), ) if err != nil { log.Fatal(err) } ctx := context.Background() result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", DocumentName: "Service Agreement", SenderName: "Acme Corp", SenderEmail: "contracts@acme.com", Recipients: []turbodocx.Recipient{ {Name: "Alice Smith", Email: "alice@example.com", SigningOrder: 1}, {Name: "Bob Johnson", Email: "bob@example.com", SigningOrder: 2}, }, Fields: []turbodocx.Field{ // Alice's signature {Type: "signature", Page: 1, X: 100, Y: 650, Width: 200, Height: 50, RecipientEmail: "alice@example.com"}, {Type: "date", Page: 1, X: 320, Y: 650, Width: 100, Height: 30, RecipientEmail: "alice@example.com"}, // Bob's signature {Type: "signature", Page: 1, X: 100, Y: 720, Width: 200, Height: 50, RecipientEmail: "bob@example.com"}, {Type: "date", Page: 1, X: 320, Y: 720, Width: 100, Height: 30, RecipientEmail: "bob@example.com"}, }, }) if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(result, "", " "); fmt.Println("Result:", string(b)) } ``` ### Using Template-Based Fields ```go result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", Recipients: []turbodocx.Recipient{ {Name: "Alice Smith", Email: "alice@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ { Type: "signature", RecipientEmail: "alice@example.com", Template: &turbodocx.TemplateAnchor{ Anchor: "{SIGNATURE_ALICE}", Placement: "replace", Size: &turbodocx.Size{Width: 200, Height: 50}, }, }, { Type: "date", RecipientEmail: "alice@example.com", Template: &turbodocx.TemplateAnchor{ Anchor: "{DATE_ALICE}", Placement: "replace", Size: &turbodocx.Size{Width: 100, Height: 30}, }, }, }, }) ``` :::info Template Anchors Required **Important:** The document file must contain the anchor text (e.g., `{SIGNATURE_ALICE}`, `{DATE_ALICE}`) that you reference in your fields. If the anchors don't exist in the document, the API will return an error. ::: --- ## File Input Methods The SDK supports multiple ways to provide your document: ### 1. File Upload ([]byte) Upload a document directly from file bytes: ```go pdfBytes, err := os.ReadFile("/path/to/document.pdf") if err != nil { log.Fatal(err) } result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ File: pdfBytes, Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"}, }, }) ``` ### 2. File URL Provide a publicly accessible URL to your document: ```go result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"}, }, }) ``` :::tip When to use FileLink Use `FileLink` when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files. ::: ### 3. TurboDocx Deliverable ID Use a document generated by TurboDocx document generation: ```go result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ DeliverableID: "deliverable-uuid-from-turbodocx", Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"}, }, }) ``` :::info Integration with TurboDocx `DeliverableID` references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign. ::: ### 4. TurboDocx Template ID Use a pre-configured TurboDocx template: ```go result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ TemplateID: "template-uuid-from-turbodocx", Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"}, }, }) ``` :::info Integration with TurboDocx `TemplateID` references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents. ::: --- ## API Reference ### Configure Create a new TurboDocx client. ```go // Simple initialization client, err := turbodocx.NewClient(apiKey, orgID string) // With custom configuration client, err := turbodocx.NewClientWithConfig(turbodocx.ClientConfig{ APIKey: "your-api-key", OrgID: "your-org-id", SenderEmail: "you@example.com", // Required for TurboSign (reply-to address) BaseURL: "https://api.turbodocx.com", // Optional }) ``` :::warning API Credentials Required `APIKey` (or `AccessToken`), `OrgID`, **and** `SenderEmail` are **required** for TurboSign operations. `SenderEmail` is used as the reply-to address for signature request emails (it can also be supplied via the `TURBODOCX_SENDER_EMAIL` environment variable). To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: ### Prepare for review Upload a document for preview without sending emails. ```go result, err := client.TurboSign.CreateSignatureReviewLink(ctx, &turbodocx.CreateSignatureReviewLinkRequest{ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", DocumentName: "Contract Draft", Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"}, }, }) b, _ := json.MarshalIndent(result, "", " "); fmt.Println("Result:", string(b)) ``` ### Prepare for signing Upload a document and immediately send signature requests. ```go result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", DocumentName: "Service Agreement", SenderName: "Your Company", SenderEmail: "sender@company.com", Recipients: []turbodocx.Recipient{ {Name: "Recipient Name", Email: "recipient@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "recipient@example.com"}, }, }) ``` ### Get status Check the status of a document. ```go status, err := client.TurboSign.GetStatus(ctx, "document-uuid") if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(status, "", " "); fmt.Println("Result:", string(b)) ``` ### Download document Download the completed signed document. ```go pdfData, err := client.TurboSign.Download(ctx, "document-uuid") if err != nil { log.Fatal(err) } // Save to file err = os.WriteFile("signed-contract.pdf", pdfData, 0644) if err != nil { log.Fatal(err) } ``` ### Get audit trail Retrieve the audit trail for a document. ```go auditTrail, err := client.TurboSign.GetAuditTrail(ctx, "document-uuid") if err != nil { log.Fatal(err) } b, _ := json.MarshalIndent(auditTrail, "", " "); fmt.Println("Result:", string(b)) ``` ### Void Cancel/void a signature request. ```go result, err := client.TurboSign.VoidDocument(ctx, "document-uuid", "Contract terms changed") ``` ### Resend Resend signature request emails. ```go // Resend to specific recipients result, err := client.TurboSign.ResendEmail(ctx, "document-uuid", []string{"recipient-uuid-1", "recipient-uuid-2"}) ``` --- ## Error Handling The SDK provides typed errors for different error scenarios: ### Error Types | Error Type | Status Code | Description | | --------------------- | ----------- | ---------------------------------- | | `TurboDocxError` | varies | Base error type for all API errors | | `AuthenticationError` | 401 | Invalid or missing API key | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Resource not found | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | ### Error Properties | Property | Type | Description | | ------------ | -------- | ---------------------------- | | `Message` | `string` | Human-readable error message | | `StatusCode` | `int` | HTTP status code | | `Code` | `string` | Error code (if available) | ### Example ```go "errors" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) result, err := client.TurboSign.SendSignature(ctx, request) if err != nil { // Check for specific error types var authErr *turbodocx.AuthenticationError var validationErr *turbodocx.ValidationError var notFoundErr *turbodocx.NotFoundError var rateLimitErr *turbodocx.RateLimitError var networkErr *turbodocx.NetworkError switch { case errors.As(err, &authErr): log.Printf("Authentication failed: %s", authErr.Message) case errors.As(err, &validationErr): log.Printf("Validation error: %s", validationErr.Message) case errors.As(err, ¬FoundErr): log.Printf("Not found: %s", notFoundErr.Message) case errors.As(err, &rateLimitErr): log.Printf("Rate limited: %s", rateLimitErr.Message) case errors.As(err, &networkErr): log.Printf("Network error: %s", networkErr.Message) default: // Base TurboDocxError or unexpected error var turboErr *turbodocx.TurboDocxError if errors.As(err, &turboErr) { log.Printf("API error [%d]: %s", turboErr.StatusCode, turboErr.Message) } else { log.Fatal(err) } } } ``` --- ## Types ### Signature Field Types The `Type` field accepts the following string values: | Type | Description | | -------------- | ---------------- | | `"signature"` | Signature field | | `"initials"` | Initials field | | `"text"` | Text input field | | `"date"` | Date field | | `"checkbox"` | Checkbox field | | `"full_name"` | Full name field | | `"first_name"` | First name field | | `"last_name"` | Last name field | | `"email"` | Email field | | `"title"` | Title field | | `"company"` | Company field | ### Recipient | Property | Type | Required | Description | | -------------- | -------- | -------- | ------------------------------------------------- | | `Name` | `string` | Yes | Recipient's full name | | `Email` | `string` | Yes | Recipient's email address | | `SigningOrder` | `int` | Yes | Order in which recipient should sign (1, 2, 3...) | ### Field | Property | Type | Required | Description | | ----------------- | ----------------- | -------- | ------------------------------------------- | | `Type` | `string` | Yes | Field type (see table above) | | `RecipientEmail` | `string` | Yes | Email of the recipient who fills this field | | `Page` | `int` | No\* | Page number (1-indexed) | | `X` | `int` | No\* | X coordinate in pixels | | `Y` | `int` | No\* | Y coordinate in pixels | | `Width` | `int` | No\* | Field width in pixels | | `Height` | `int` | No\* | Field height in pixels | | `DefaultValue` | `string` | No | Pre-filled value | | `IsMultiline` | `bool` | No | Enable multiline for text fields | | `IsReadonly` | `bool` | No | Make field read-only | | `Required` | `bool` | No | Make field required | | `BackgroundColor` | `string` | No | Background color | | `Template` | `*TemplateAnchor` | No | Template anchor configuration | \*Required when not using template anchors #### Template Configuration When using `Template` instead of coordinates: | Property | Type | Required | Description | | --------------- | -------- | -------- | ------------------------------------------------------------------------------------- | | `Anchor` | `string` | Yes | Text to find in document (e.g., `"{SIGNATURE}"`) | | `Placement` | `string` | Yes | Position relative to anchor: `"replace"`, `"before"`, `"after"`, `"above"`, `"below"` | | `Size` | `*Size` | Yes | Size with `Width` and `Height` | | `Offset` | `*Point` | No | Offset with `X` and `Y` | | `CaseSensitive` | `bool` | No | Case-sensitive anchor search | | `UseRegex` | `bool` | No | Use regex for anchor search | ### Request Parameters Both `CreateSignatureReviewLinkRequest` and `SendSignatureRequest` accept: | Property | Type | Required | Description | | --------------------- | ------------- | ----------- | ------------------------ | | `File` | `[]byte` | Conditional | File content as bytes | | `FileLink` | `string` | Conditional | URL to document | | `DeliverableID` | `string` | Conditional | TurboDocx deliverable ID | | `TemplateID` | `string` | Conditional | TurboDocx template ID | | `Recipients` | `[]Recipient` | Yes | List of recipients | | `Fields` | `[]Field` | Yes | List of fields | | `DocumentName` | `string` | No | Document display name | | `DocumentDescription` | `string` | No | Document description | | `SenderName` | `string` | No | Sender's name | | `SenderEmail` | `string` | No | Sender's email | | `CCEmails` | `[]string` | No | CC email addresses | :::info File Source (Conditional) Exactly one file source is required: `File`, `FileLink`, `DeliverableID`, or `TemplateID`. ::: --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[Request Body Reference](/docs/TurboSign/API%20Signatures#request-body-multipartform-data)** - Complete request body parameters, file sources, and multipart/form-data structure - **[Recipients Reference](/docs/TurboSign/API%20Signatures#recipients-reference)** - Recipient properties, signing order, metadata, and configuration options - **[Field Types Reference](/docs/TurboSign/API%20Signatures#field-types-reference)** - All available field types (signature, date, text, checkbox, etc.) with properties and behaviors - **[Field Positioning Methods](/docs/TurboSign/API%20Signatures#field-positioning-methods)** - Template-based vs coordinate-based positioning, anchor configuration, and best practices --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) - [API Reference](/docs/TurboSign/API-Signatures) - [Webhook Configuration](/docs/TurboSign/Webhooks) --- ## SDKs Overview # TurboDocx SDKs Official client libraries for the TurboDocx API. Build document generation, digital signature, and quoting workflows in your language of choice. ## Choose Your Product All five modules ship in the **same package** for each language — pick the one that matches what you're building: | Product | Use it when you need to… | | :------------- | :----------------------------------------------------------------------------------------- | | **TurboSign** | Send documents for legally-binding e-signature; track status; download signed PDFs | | **Deliverable** | Generate documents from templates with variable injection (DOCX / PPTX / PDF output) | | **TurboQuote** | Build sales quotes & proposals (CPQ): line items, a product/bundle catalog, price books | | **TurboWebhooks** | Receive real-time signature events instead of polling, and verify inbound deliveries | TurboSign, Deliverable, TurboQuote, and TurboWebhooks all use the same `TURBODOCX_API_KEY` + `TURBODOCX_ORG_ID`. See [credential requirements](#which-credentials-does-each-product-need) below. ## TurboSign SDKs Send documents for legally-binding eSignatures with full audit trails. | Language | Package | Install Command | Links | | :------------------------ | :------------------------- | :------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | | **JavaScript/TypeScript** | `@turbodocx/sdk` | `npm install @turbodocx/sdk` | [Docs](/docs/SDKs/javascript) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) | | **Python** | `turbodocx-sdk` | `pip install turbodocx-sdk` | [Docs](/docs/SDKs/python) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) | | **PHP** | `turbodocx/sdk` | `composer require turbodocx/sdk` | [Docs](/docs/SDKs/php) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) | | **Go** | `github.com/TurboDocx/SDK/packages/go-sdk` | `go get github.com/TurboDocx/SDK/packages/go-sdk` | [Docs](/docs/SDKs/go) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) | | **Java** | `com.turbodocx:turbodocx-sdk` | [Maven Central](https://search.maven.org/artifact/com.turbodocx/turbodocx-sdk) | [Docs](/docs/SDKs/java) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) | ## TurboWebhooks SDKs Subscribe to TurboSign events (`signature.document.completed`, `signature.document.voided`) and verify inbound signatures with HMAC-SHA256. | Language | Package | Install Command | Links | | :------------------------ | :-------------- | :---------------------------- | :----------------------------------------------------------------------------------------------------- | | **JavaScript / TypeScript** | `@turbodocx/sdk` | `npm install @turbodocx/sdk` | [Docs](/docs/SDKs/webhooks-javascript) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) | | **PHP** | `turbodocx/sdk` | `composer require turbodocx/sdk` | [Docs](/docs/SDKs/webhooks-php) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) | | **Python** | `turbodocx-sdk` | `pip install turbodocx-sdk` | [Docs](/docs/SDKs/webhooks-python) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) | | **Go** | `github.com/TurboDocx/SDK/packages/go-sdk` | `go get github.com/TurboDocx/SDK/packages/go-sdk` | [Docs](/docs/SDKs/webhooks-go) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) | | **Java** | `com.turbodocx:turbodocx-sdk` | `mvn` / `gradle` (see [docs](/docs/SDKs/webhooks-java)) | [Docs](/docs/SDKs/webhooks-java) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) | For the conceptual overview (delivery retries, payload schema, dashboard configuration), see [TurboSign → Webhooks](/docs/TurboSign/Webhooks). ## Deliverable SDKs Generate documents from templates with dynamic variable injection, download source files and PDFs. | Language | Package | Install Command | Links | | :------------------------ | :------------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | | **JavaScript/TypeScript** | `@turbodocx/sdk` | `npm install @turbodocx/sdk` | [Docs](/docs/SDKs/deliverable-javascript) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) | | **Python** | `turbodocx-sdk` | `pip install turbodocx-sdk` | [Docs](/docs/SDKs/deliverable-python) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) | | **PHP** | `turbodocx/sdk` | `composer require turbodocx/sdk` | [Docs](/docs/SDKs/deliverable-php) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) | | **Go** | `github.com/TurboDocx/SDK/packages/go-sdk` | `go get github.com/TurboDocx/SDK/packages/go-sdk` | [Docs](/docs/SDKs/deliverable-go) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) | | **Java** | `com.turbodocx:turbodocx-sdk` | [Maven Central](https://search.maven.org/artifact/com.turbodocx/turbodocx-sdk) | [Docs](/docs/SDKs/deliverable-java) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) | ## TurboQuote SDKs Build sales quotes and proposals programmatically: quotes and line items, a product/bundle catalog, price books, companies/contacts, and quote templates. | Language | Package | Install Command | Links | | :------------------------ | :------------------------- | :------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | | **JavaScript/TypeScript** | `@turbodocx/sdk` | `npm install @turbodocx/sdk` | [Docs](/docs/SDKs/quote-javascript) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) | | **Python** | `turbodocx-sdk` | `pip install turbodocx-sdk` | [Docs](/docs/SDKs/quote-python) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) | | **PHP** | `turbodocx/sdk` | `composer require turbodocx/sdk` | [Docs](/docs/SDKs/quote-php) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) | | **Go** | `github.com/TurboDocx/SDK/packages/go-sdk` | `go get github.com/TurboDocx/SDK/packages/go-sdk` | [Docs](/docs/SDKs/quote-go) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) | | **Java** | `com.turbodocx:turbodocx-sdk` | [Maven Central](https://search.maven.org/artifact/com.turbodocx/turbodocx-sdk) | [Docs](/docs/SDKs/quote-java) [GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) | :::tip Low-code or No-code? Check out our [n8n community node](https://www.npmjs.com/package/@turbodocx/n8n-nodes-turbodocx) for workflow automation, or get [TurboDocx Writer](https://appsource.microsoft.com/en-us/product/office/WA200007397) for Microsoft Word. ::: --- ## Quick Start Get up and running in under 2 minutes. ### 1. Get Your Credentials Before you begin, you'll need two things from your TurboDocx account: - **API Access Token**: Your authentication key - **Organization ID**: Your unique organization identifier :::note senderEmail required for TurboSign TurboSign also requires a `senderEmail` (used as the reply-to address for signature request emails). The SDK throws a validation error if it is missing. It can be passed in the SDK configuration or supplied via the `TURBODOCX_SENDER_EMAIL` environment variable. Deliverable, TurboQuote, and TurboWebhooks do not require it. ::: #### Which credentials does each product need? | Product | API key | Org ID | Also needs | | :------------- | :----------------------------- | :------------------------- | :-------------------------------------------------------------- | | **TurboSign** | `TURBODOCX_API_KEY` | `TURBODOCX_ORG_ID` | `TURBODOCX_SENDER_EMAIL` (required — reply-to for signer emails) | | **Deliverable** | `TURBODOCX_API_KEY` | `TURBODOCX_ORG_ID` | — | | **TurboQuote** | `TURBODOCX_API_KEY` | `TURBODOCX_ORG_ID` | — | | **TurboWebhooks** | `TURBODOCX_API_KEY` (**administrator** role — non-admin keys get 403) | `TURBODOCX_ORG_ID` | the webhook secret returned by `createWebhook`, to verify inbound events | #### How to Get 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 copy your API access token 4. **Organization ID**: Copy your organization ID from the same settings page ![TurboSign API Key](/img/turbosign/api/api-key.png) ![TurboSign Organization ID](/img/turbosign/api/org-id.png) :::tip Keep Your Credentials Secure - Store your API key and Organization ID as environment variables - Never commit credentials to version control - Rotate your API keys regularly for security ::: ### 2. Install the SDK ```bash npm install @turbodocx/sdk # or yarn add @turbodocx/sdk # or pnpm add @turbodocx/sdk ``` ```bash npm install @turbodocx/sdk # or yarn add @turbodocx/sdk # or pnpm add @turbodocx/sdk # TypeScript types are included in the package ``` ```bash pip install turbodocx-sdk # or poetry add turbodocx-sdk ``` ```bash composer require turbodocx/sdk ``` ```bash go get github.com/TurboDocx/SDK/packages/go-sdk ``` ```xml com.turbodocx turbodocx-sdk 0.4.0 ``` ### 3. Send Your First Document for Signature ```javascript const { TurboSign } = require("@turbodocx/sdk"); // or with ES modules: // import { TurboSign } from '@turbodocx/sdk'; // Configure with your API key TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // required for TurboSign }); (async () => { // Send a document for signature const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); console.log(`Document sent! ID: ${result.documentId}`); })(); ``` ```typescript // Configure with your API key TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", orgId: process.env.TURBODOCX_ORG_ID || "", senderEmail: process.env.TURBODOCX_SENDER_EMAIL || "", // required for TurboSign }); // Send a document for signature const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); console.log(`Document sent! ID: ${result.documentId}`); ``` ```python from turbodocx_sdk import TurboSign # Configure with your API key TurboSign.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], sender_email=os.environ["TURBODOCX_SENDER_EMAIL"] # required for TurboSign ) async def main(): # Send a document for signature result = await TurboSign.send_signature( file_link="https://www.turbodocx.com/examples/turbodocx.pdf", recipients=[ {"name": "John Doe", "email": "john@example.com", "signingOrder": 1} ], fields=[ {"type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "john@example.com"} ] ) print(f"Document sent! ID: {result['documentId']}") asyncio.run(main()) ``` ```php documentId}\n"; ``` ```go package main "context" "fmt" "log" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { // Configure with your API key client, err := turbodocx.NewClient( os.Getenv("TURBODOCX_API_KEY"), os.Getenv("TURBODOCX_ORG_ID"), ) if err != nil { log.Fatal(err) } // Send a document for signature result, err := client.TurboSign.SendSignature(context.Background(), &turbodocx.SendSignatureRequest{ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", SenderEmail: "contracts@acme.com", // required for TurboSign Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"}, }, }) if err != nil { log.Fatal(err) } fmt.Printf("Document sent! ID: %s\n", result.DocumentID) } ``` ```java public class Main { public static void main(String[] args) throws Exception { // Configure with your API key TurboDocxClient client = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL")) // required for TurboSign .build(); // Send a document for signature SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf") .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "john@example.com") )) .build() ); System.out.println("Document sent! ID: " + result.getDocumentId()); } } ``` --- ## Core Features All TurboDocx SDKs provide access to: ### TurboSign — Digital Signatures Send documents for legally-binding eSignatures with full audit trails. | Method | Description | | :---------------------------- | :------------------------------------------------------ | | `createSignatureReviewLink()` | Upload document for preview without sending emails | | `sendSignature()` | Upload and immediately send signature requests | | `getStatus()` | Check document and recipient signing status | | `download()` | Download the completed signed document | | `void()` | Cancel/void a signature request | | `resend()` | Resend signature request emails | | `getAuditTrail()` | Get complete audit trail with all events and timestamps | [Learn more about TurboSign →](/docs/TurboSign/Setting%20up%20TurboSign) ### Deliverable — Document Generation Generate documents from templates with dynamic variable injection, download source files and PDFs. | Method | Description | | :-------------------------- | :--------------------------------------------------------- | | `generateDeliverable()` | Generate a document from a template with variable injection | | `listDeliverables()` | List deliverables with pagination, search, and filtering | | `getDeliverableDetails()` | Get full details of a deliverable including variables | | `updateDeliverableInfo()` | Update a deliverable's name, description, or tags | | `deleteDeliverable()` | Soft-delete a deliverable | | `downloadSourceFile()` | Download the original DOCX/PPTX source file | | `downloadPDF()` | Download the PDF version | [Learn more about Deliverable SDKs →](/docs/SDKs/deliverable-javascript) ### TurboQuote — Sales Quoting & CPQ Build quotes and proposals: line items, a product/bundle catalog, price books, companies, and contacts. | Method | Description | | :--------------------------- | :---------------------------------------------------------------- | | `createQuote()` | Create a draft quote for a company and contact | | `addLineItems()` | Add product or bundle line items to a quote | | `sendQuote()` | Send a quote to the customer for review | | `sendQuoteWithDeliverable()` | Merge a TurboDocx Deliverable with the quote and send for e-signature | | `downloadQuotePdf()` | Download the rendered quote PDF | | `createProduct()` / `createBundle()` / `createPriceBook()` | Manage the product catalog and pricing | | `createAndSend()` | Create, add line items, and send in a single call | [Learn more about TurboQuote SDKs →](/docs/SDKs/quote-javascript) ### TurboWebhooks — Signature Events Subscribe a per-org endpoint to TurboSign events and verify inbound deliveries with HMAC-SHA256. **Requires an administrator API key.** | Method | Description | | :--------------------------- | :--------------------------------------------------------------- | | `createWebhook()` | Subscribe the org's `signature` webhook (returns the secret once) | | `getWebhook()` | Get the webhook plus delivery stats | | `updateWebhook()` | Update URLs, events, or active state | | `testWebhook()` | Fire a synthetic delivery to all configured URLs | | `regenerateWebhookSecret()` | Rotate the HMAC secret | | `listWebhookDeliveries()` / `replayWebhookDelivery()` | Inspect and retry past deliveries | | `verifyWebhookSignature()` | Free function — verify the `X-TurboDocx-Signature` header on a received event | [Learn more about TurboWebhooks SDKs →](/docs/SDKs/webhooks-javascript) --- ## Field Positioning TurboSign supports two methods for placing signature fields on your documents: | Method | Best For | | :------------------- | :----------------------------------------------------------------------- | | **Coordinate-Based** | PDFs with fixed layouts where you know exact pixel positions | | **Template-Based** | Documents where content may shift, using text anchors like `{SIGNATURE}` | :::info Field Positioning Reference For detailed information about both positioning methods, including anchor configuration, placement options, and best practices, see the **[Field Positioning Methods](/docs/TurboSign/API%20Signatures#field-positioning-methods)** guide. ::: :::info Complete Field Types Reference For a comprehensive list of all available field types (signature, initials, text, date, checkbox, full_name, email, title, company) and their detailed usage, see the [Field Types section in the API Signatures guide](/docs/TurboSign/API%20Signatures#field-types-reference). ::: --- ## Error Handling All SDKs provide structured error handling with detailed error codes: ```javascript const { TurboSign, TurboDocxError } = require("@turbodocx/sdk"); (async () => { try { const result = await TurboSign.sendSignature({ /* ... */ }); } catch (error) { if (error instanceof TurboDocxError) { console.error(`Error ${error.code}: ${error.message}`); // Handle specific error codes if (error.code === "VALIDATION_ERROR") { // Handle validation error } } } })(); ``` ```typescript try { const result = await TurboSign.sendSignature({ /* ... */ }); } catch (error) { if (error instanceof TurboDocxError) { console.error(`Error ${error.code}: ${error.message}`); // Handle specific error codes if (error.code === "VALIDATION_ERROR") { // Handle validation error } } } ``` ```python from turbodocx_sdk import TurboSign, TurboDocxError async def main(): try: result = await TurboSign.send_signature(...) except TurboDocxError as e: print(f"Error {e.code}: {e.message}") if e.code == "VALIDATION_ERROR": # Handle validation error pass asyncio.run(main()) ``` ```php getMessage()}\n"; // Handle validation error } catch (TurboDocxException $e) { echo "Error {$e->getCode()}: {$e->getMessage()}\n"; echo "Status code: {$e->statusCode}\n"; } ``` ```go result, err := client.TurboSign.SendSignature(ctx, request) if err != nil { var turboErr *sdk.TurboDocxError if errors.As(err, &turboErr) { fmt.Printf("Error %s: %s\n", turboErr.Code, turboErr.Message) if turboErr.Code == "VALIDATION_ERROR" { // Handle validation error } } } ``` ```java try { SigningResult result = turboSign.sendSignature(/* ... */); } catch (AuthenticationException e) { System.err.println("Invalid API key: " + e.getMessage()); } catch (ValidationException e) { System.err.println("Validation error: " + e.getMessage()); System.err.println("Error code: " + e.getCode()); } catch (RateLimitException e) { System.err.println("Rate limited: " + e.getMessage()); } catch (TurboDocxException e) { System.err.println("Error " + e.getCode() + ": " + e.getMessage()); System.err.println("Status code: " + e.getStatusCode()); } ``` ### Common Error Codes | Code | HTTP Status | Description | | :--------------------- | :---------- | :------------------------------------ | | `AUTHENTICATION_ERROR` | 401 | Invalid or expired API key | | `VALIDATION_ERROR` | 400 | Invalid request data or parameters | | `NOT_FOUND` | 404 | Document or resource not found | | `RATE_LIMIT_EXCEEDED` | 429 | Too many requests, retry with backoff | | `NETWORK_ERROR` | N/A | Network connection or timeout error | --- ## Next Steps { e.currentTarget.style.transform = 'translateY(-8px)'; e.currentTarget.style.boxShadow = '0 12px 24px rgba(0, 0, 0, 0.2), 0 6px 12px rgba(0, 0, 0, 0.15)'; }} onMouseOut={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06)'; }} > API Signatures Complete guide to TurboSign API integration with detailed examples. { e.currentTarget.style.transform = 'scale(1.02)'; e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.25)'; e.currentTarget.style.background = 'linear-gradient(135deg, var(--ifm-color-primary-dark) 0%, var(--ifm-color-primary-darker) 100%)'; }} onMouseOut={(e) => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; e.currentTarget.style.background = 'linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-dark) 100%)'; }} > View Guide → { e.currentTarget.style.transform = 'translateY(-8px)'; e.currentTarget.style.boxShadow = '0 12px 24px rgba(0, 0, 0, 0.2), 0 6px 12px rgba(0, 0, 0, 0.15)'; }} onMouseOut={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06)'; }} > Webhooks Receive real-time notifications when documents are signed. { e.currentTarget.style.transform = 'scale(1.02)'; e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.25)'; e.currentTarget.style.background = 'linear-gradient(135deg, var(--ifm-color-primary-dark) 0%, var(--ifm-color-primary-darker) 100%)'; }} onMouseOut={(e) => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; e.currentTarget.style.background = 'linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-dark) 100%)'; }} > Configure Webhooks → { e.currentTarget.style.transform = 'translateY(-8px)'; e.currentTarget.style.boxShadow = '0 12px 24px rgba(0, 0, 0, 0.2), 0 6px 12px rgba(0, 0, 0, 0.15)'; }} onMouseOut={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06)'; }} > GitHub Repository View source code, report issues, and contribute to the SDKs. { e.currentTarget.style.transform = 'scale(1.02)'; e.currentTarget.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.25)'; e.currentTarget.style.background = 'linear-gradient(135deg, var(--ifm-color-primary-dark) 0%, var(--ifm-color-primary-darker) 100%)'; }} onMouseOut={(e) => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; e.currentTarget.style.background = 'linear-gradient(135deg, var(--ifm-color-primary) 0%, var(--ifm-color-primary-dark) 100%)'; }} > View on GitHub → --- ## Support Need help with the SDKs? - **Discord Community**: [Join our Discord](https://discord.gg/NYKwz4BcpX) for real-time support - **GitHub Issues**: [Report bugs or request features](https://github.com/TurboDocx/SDK/issues) - **Documentation**: Browse the language-specific guides in the sidebar --- # Java SDK The official TurboDocx SDK for Java applications. Build document generation and digital signature workflows with the Builder pattern, comprehensive error handling, and type-safe APIs. Available on Maven Central as `com.turbodocx:turbodocx-sdk`. ## Installation ```xml com.turbodocx turbodocx-sdk 0.4.0 ``` ```kotlin implementation("com.turbodocx:turbodocx-sdk:0.4.0") ``` ```groovy implementation 'com.turbodocx:turbodocx-sdk:0.4.0' ``` ## Requirements - Java 11+ - OkHttp 4.x (included) - Gson 2.x (included) --- ## Configuration ```java public class Main { public static void main(String[] args) { // Create client with Builder pattern TurboDocxClient client = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL")) .build(); // Or with custom base URL TurboDocxClient client = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL")) .baseUrl("https://api.turbodocx.com") .build(); } } ``` ### Environment Variables ```bash export TURBODOCX_API_KEY=your_api_key_here export TURBODOCX_ORG_ID=your_org_id_here export TURBODOCX_SENDER_EMAIL=sender@yourcompany.com ``` :::warning API Credentials Required Three parameters are **required** for TurboSign operations: `apiKey` (or `accessToken`), `orgId`, and `senderEmail`. The `senderEmail` is used as the reply-to address for signature request emails. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Send a Document for Signature ```java public class Main { public static void main(String[] args) throws Exception { TurboDocxClient client = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL")) .build(); Gson gson = new GsonBuilder().setPrettyPrinting().create(); SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf") .documentName("Service Agreement") .senderName("Acme Corp") .senderEmail("contracts@acme.com") .recipients(Arrays.asList( new Recipient("Alice Smith", "alice@example.com", 1), new Recipient("Bob Johnson", "bob@example.com", 2) )) .fields(Arrays.asList( // Alice's signature new Field("signature", 1, 100, 650, 200, 50, "alice@example.com"), new Field("date", 1, 320, 650, 100, 30, "alice@example.com"), // Bob's signature new Field("signature", 1, 100, 720, 200, 50, "bob@example.com"), new Field("date", 1, 320, 720, 100, 30, "bob@example.com") )) .build() ); System.out.println("Result: " + gson.toJson(result)); } } ``` ### Using Template-Based Fields ```java // Template-based field using anchor text Field.TemplateAnchor templateAnchor = new Field.TemplateAnchor( "{SIGNATURE_ALICE}", // anchor text to find null, // searchText (alternative to anchor) "replace", // placement: replace/before/after/above/below new Field.Size(200, 50), // size null, // offset false, // caseSensitive false // useRegex ); // Field with template anchor (no page/x/y coordinates needed) Field templateField = new Field( "signature", // type null, // page (null for template-based) null, // x (null for template-based) null, // y (null for template-based) null, // width (null, using template size) null, // height (null, using template size) "alice@example.com", // recipientEmail null, // defaultValue null, // isMultiline null, // isReadonly null, // required null, // backgroundColor templateAnchor // template anchor config ); SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf") .recipients(Arrays.asList( new Recipient("Alice Smith", "alice@example.com", 1) )) .fields(Arrays.asList(templateField)) .build() ); ``` :::info Template Anchors Required **Important:** The document file must contain the anchor text (e.g., `{SIGNATURE_ALICE}`, `{DATE_ALICE}`) that you reference in your fields. If the anchors don't exist in the document, the API will return an error. ::: --- ## File Input Methods The SDK supports multiple ways to provide your document: ### 1. File Upload (byte[]) Upload a document directly from file bytes: ```java byte[] pdfBytes = Files.readAllBytes(Paths.get("/path/to/document.pdf")); SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .file(pdfBytes) .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "john@example.com") )) .build() ); ``` ### 2. File URL Provide a publicly accessible URL to your document: ```java SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf") .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "john@example.com") )) .build() ); ``` :::tip When to use fileLink Use `fileLink` when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files. ::: ### 3. TurboDocx Deliverable ID Use a document generated by TurboDocx document generation: ```java SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .deliverableId("deliverable-uuid-from-turbodocx") .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "john@example.com") )) .build() ); ``` :::info Integration with TurboDocx `deliverableId` references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign. ::: ### 4. TurboDocx Template ID Use a pre-configured TurboDocx template: ```java SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .templateId("template-uuid-from-turbodocx") .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "john@example.com") )) .build() ); ``` :::info Integration with TurboDocx `templateId` references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents. ::: --- ## API Reference :::note The snippets below assume a configured `client` and an available `Gson` instance, e.g. `Gson gson = new GsonBuilder().setPrettyPrinting().create();` (as shown in the [Quick Start](#quick-start)). ::: ### Configure Create a new TurboDocx client using the Builder pattern. ```java TurboDocxClient client = new TurboDocxClient.Builder() .apiKey("your-api-key") // Required .orgId("your-org-id") // Required .senderEmail("sender@yourcompany.com") // Required for TurboSign .baseUrl("https://api.turbodocx.com") // Optional .build(); ``` ### Prepare for review Upload a document for preview without sending emails. ```java CreateSignatureReviewLinkResponse result = client.turboSign().createSignatureReviewLink( new CreateSignatureReviewLinkRequest.Builder() .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf") .documentName("Contract Draft") .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "john@example.com") )) .build() ); System.out.println("Result: " + gson.toJson(result)); ``` ### Prepare for signing Upload a document and immediately send signature requests. ```java SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf") .documentName("Service Agreement") .senderName("Your Company") .senderEmail("sender@company.com") .recipients(Arrays.asList( new Recipient("Recipient Name", "recipient@example.com", 1) )) .fields(Arrays.asList( new Field("signature", 1, 100, 500, 200, 50, "recipient@example.com") )) .build() ); System.out.println("Result: " + gson.toJson(result)); ``` ### Get status Check the status of a document. ```java DocumentStatusResponse status = client.turboSign().getStatus("document-uuid"); System.out.println("Result: " + gson.toJson(status)); ``` ### Download document Download the completed signed document. ```java byte[] pdfData = client.turboSign().download("document-uuid"); // Save to file Files.write(Paths.get("signed-contract.pdf"), pdfData); ``` ### Get audit trail Retrieve the audit trail for a document. ```java AuditTrailResponse auditTrail = client.turboSign().getAuditTrail("document-uuid"); System.out.println("Result: " + gson.toJson(auditTrail)); ``` ### Void Cancel/void a signature request. ```java VoidDocumentResponse result = client.turboSign().voidDocument("document-uuid", "Contract terms changed"); ``` ### Resend Resend signature request emails. ```java // Resend to specific recipients ResendEmailResponse result = client.turboSign().resendEmail( "document-uuid", Arrays.asList("recipient-uuid-1", "recipient-uuid-2") ); ``` --- ## Error Handling The SDK provides typed exceptions for different error scenarios: ### Error Types | Error Type | Status Code | Description | | -------------------------------------------- | ----------- | ---------------------------------- | | `TurboDocxException` | varies | Base exception for all API errors | | `TurboDocxException.AuthenticationException` | 401 | Invalid or missing API credentials | | `TurboDocxException.ValidationException` | 400 | Invalid request parameters | | `TurboDocxException.AuthorizationException` | 403 | Authenticated but lacks permissions for the route | | `TurboDocxException.NotFoundException` | 404 | Document or resource not found | | `TurboDocxException.ConflictException` | 409 | Request conflicts with current state of resource (e.g., webhook name already exists) | | `TurboDocxException.RateLimitException` | 429 | Too many requests | | `TurboDocxException.NetworkException` | - | Network connectivity issues | ### Error Properties | Property | Type | Description | | ----------------- | -------- | ---------------------------- | | `getMessage()` | `String` | Human-readable error message | | `getStatusCode()` | `int` | HTTP status code | | `getCode()` | `String` | Error code (if available) | ### Example ```java try { SendSignatureResponse result = client.turboSign().sendSignature(request); } catch (TurboDocxException.AuthenticationException e) { System.err.println("Authentication failed: " + e.getMessage()); // Check your API key and org ID } catch (TurboDocxException.ValidationException e) { System.err.println("Validation error: " + e.getMessage()); // Check request parameters } catch (TurboDocxException.NotFoundException e) { System.err.println("Not found: " + e.getMessage()); // Document or recipient doesn't exist } catch (TurboDocxException.RateLimitException e) { System.err.println("Rate limited: " + e.getMessage()); // Wait and retry } catch (TurboDocxException.NetworkException e) { System.err.println("Network error: " + e.getMessage()); // Check connectivity } catch (TurboDocxException e) { // Base exception for other API errors System.err.println("API error [" + e.getStatusCode() + "]: " + e.getMessage()); } ``` --- ## Types ### Signature Field Types The `type` field accepts the following string values: | Type | Description | | -------------- | ---------------- | | `"signature"` | Signature field | | `"initial"` | Initials field | | `"text"` | Text input field | | `"date"` | Date field | | `"checkbox"` | Checkbox field | | `"full_name"` | Full name field | | `"first_name"` | First name field | | `"last_name"` | Last name field | | `"email"` | Email field | | `"title"` | Title field | | `"company"` | Company field | ### Recipient | Property | Type | Required | Description | | -------------- | -------- | -------- | ------------------------------------------------- | | `name` | `String` | Yes | Recipient's full name | | `email` | `String` | Yes | Recipient's email address | | `signingOrder` | `int` | Yes | Order in which recipient should sign (1, 2, 3...) | ### Field The coordinate-based constructor takes positional arguments in this order: `new Field(type, page, x, y, width, height, recipientEmail)`. For template-based fields, use the extended constructor shown in [Using Template-Based Fields](#using-template-based-fields). | Property | Type | Required | Description | | ----------------- | ---------------- | -------- | ------------------------------------------- | | `type` | `String` | Yes | Field type (see table above) | | `recipientEmail` | `String` | Yes | Email of the recipient who fills this field | | `page` | `Integer` | No\* | Page number (1-indexed) | | `x` | `Integer` | No\* | X coordinate in pixels | | `y` | `Integer` | No\* | Y coordinate in pixels | | `width` | `Integer` | No\* | Field width in pixels | | `height` | `Integer` | No\* | Field height in pixels | | `defaultValue` | `String` | No | Pre-filled value | | `isMultiline` | `Boolean` | No | Enable multiline for text fields | | `isReadonly` | `Boolean` | No | Make field read-only | | `required` | `Boolean` | No | Make field required | | `backgroundColor` | `String` | No | Background color | | `template` | `TemplateAnchor` | No | Template anchor configuration | \*Required when not using template anchors #### Template Configuration When using `template` instead of coordinates: | Property | Type | Required | Description | | --------------- | --------- | -------- | ------------------------------------------------------------------------------------- | --- | | `anchor` | `String` | Yes | Text to find in document (e.g., `"{SIGNATURE}"`) | | | `placement` | `String` | Yes | Position relative to anchor: `"replace"`, `"before"`, `"after"`, `"above"`, `"below"` | | `size` | `Size` | Yes | Size with `width` and `height` | | `offset` | `Offset` | No | Offset with `x` and `y` | | `caseSensitive` | `Boolean` | No | Case-sensitive anchor search | | `useRegex` | `Boolean` | No | Use regex for anchor search | ### Request Parameters Both `CreateSignatureReviewLinkRequest` and `SendSignatureRequest` accept: | Property | Type | Required | Description | | --------------------- | ----------------- | ----------- | ------------------------ | | `file` | `byte[]` | Conditional | File content as bytes | | `fileLink` | `String` | Conditional | URL to document | | `deliverableId` | `String` | Conditional | TurboDocx deliverable ID | | `templateId` | `String` | Conditional | TurboDocx template ID | | `recipients` | `List` | Yes | List of recipients | | `fields` | `List` | Yes | List of fields | | `documentName` | `String` | No | Document display name | | `documentDescription` | `String` | No | Document description | | `senderName` | `String` | No | Sender's name | | `senderEmail` | `String` | No | Sender's email | | `ccEmails` | `List` | No | CC email addresses | :::info File Source (Conditional) Exactly one file source is required: `file`, `fileLink`, `deliverableId`, or `templateId`. ::: --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[Request Body Reference](/docs/TurboSign/API%20Signatures#request-body-multipartform-data)** - Complete request body parameters, file sources, and multipart/form-data structure - **[Recipients Reference](/docs/TurboSign/API%20Signatures#recipients-reference)** - Recipient properties, signing order, metadata, and configuration options - **[Field Types Reference](/docs/TurboSign/API%20Signatures#field-types-reference)** - All available field types (signature, date, text, checkbox, etc.) with properties and behaviors - **[Field Positioning Methods](/docs/TurboSign/API%20Signatures#field-positioning-methods)** - Template-based vs coordinate-based positioning, anchor configuration, and best practices --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) - [Maven Central](https://search.maven.org/artifact/com.turbodocx/turbodocx-sdk) - [API Reference](/docs/TurboSign/API-Signatures) - [Webhook Configuration](/docs/TurboSign/Webhooks) --- # JavaScript / TypeScript SDK The official TurboDocx SDK for JavaScript and TypeScript applications. Build document generation and digital signature workflows with full TypeScript support, async/await patterns, and comprehensive error handling. Available on npm as `@turbodocx/sdk`. ## Installation ```bash npm install @turbodocx/sdk ``` ```bash yarn add @turbodocx/sdk ``` ```bash pnpm add @turbodocx/sdk ``` ## Requirements - Node.js 18+ or modern browser - TypeScript 4.7+ (optional, for type checking) --- ## Configuration ```javascript const { TurboSign } = require("@turbodocx/sdk"); // Configure globally (recommended for server-side) TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY, // Required : Your TurboDocx API key orgId: process.env.TURBODOCX_ORG_ID, // Required: Your organization ID senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // Required: reply-to address for signature request emails senderName: "Your Company Name", // Optional but recommended: appears as the sender name // Optional: override base URL for testing // baseUrl: 'https://api.turbodocx.com' }); ``` ```typescript // Configure globally (recommended for server-side) TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", // Required : Your TurboDocx API key orgId: process.env.TURBODOCX_ORG_ID || "", // Required: Your organization ID senderEmail: process.env.TURBODOCX_SENDER_EMAIL || "", // Required: reply-to address for signature request emails senderName: "Your Company Name", // Optional but recommended: appears as the sender name // Optional: override base URL for testing // baseUrl: 'https://api.turbodocx.com' }); ``` :::tip Authentication Authenticate using `apiKey`. API keys are recommended for server-side applications. ::: ### Environment Variables ```bash # .env TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here ``` --- ## Quick Start ### Send a Document for Signature ```javascript const { TurboSign } = require("@turbodocx/sdk"); TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, senderEmail: process.env.TURBODOCX_SENDER_EMAIL, }); (async () => { // Send document with coordinate-based fields const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", documentName: "Service Agreement", senderName: "Acme Corp", senderEmail: "contracts@acme.com", recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, { name: "Bob Johnson", email: "bob@example.com", signingOrder: 2 }, ], fields: [ // Alice's signature { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "alice@example.com", }, { type: "date", page: 1, x: 320, y: 650, width: 100, height: 30, recipientEmail: "alice@example.com", }, // Bob's signature { type: "signature", page: 1, x: 100, y: 720, width: 200, height: 50, recipientEmail: "bob@example.com", }, { type: "date", page: 1, x: 320, y: 720, width: 100, height: 30, recipientEmail: "bob@example.com", }, ], }); console.log(JSON.stringify(result, null, 2)); })(); ``` ```typescript TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", orgId: process.env.TURBODOCX_ORG_ID || "", senderEmail: process.env.TURBODOCX_SENDER_EMAIL || "", }); // Send document with coordinate-based fields const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", documentName: "Service Agreement", senderName: "Acme Corp", senderEmail: "contracts@acme.com", recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, { name: "Bob Johnson", email: "bob@example.com", signingOrder: 2 }, ], fields: [ // Alice's signature { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "alice@example.com", }, { type: "date", page: 1, x: 320, y: 650, width: 100, height: 30, recipientEmail: "alice@example.com", }, // Bob's signature { type: "signature", page: 1, x: 100, y: 720, width: 200, height: 50, recipientEmail: "bob@example.com", }, { type: "date", page: 1, x: 320, y: 720, width: 100, height: 30, recipientEmail: "bob@example.com", }, ], }); console.log(JSON.stringify(result, null, 2)); ``` ### Using Template-Based Fields ```javascript // Use text anchors instead of coordinates const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", recipientEmail: "alice@example.com", template: { anchor: "{SIGNATURE_ALICE}", placement: "replace", size: { width: 200, height: 50 }, }, }, { type: "date", recipientEmail: "alice@example.com", template: { anchor: "{DATE_ALICE}", placement: "replace", size: { width: 100, height: 30 }, }, }, ], }); console.log(JSON.stringify(result, null, 2)); ``` ```typescript // Use text anchors instead of coordinates const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", recipientEmail: "alice@example.com", template: { anchor: "{SIGNATURE_ALICE}", placement: "replace", size: { width: 200, height: 50 }, }, }, { type: "date", recipientEmail: "alice@example.com", template: { anchor: "{DATE_ALICE}", placement: "replace", size: { width: 100, height: 30 }, }, }, ], }); console.log(JSON.stringify(result, null, 2)); ``` :::info Template Anchors Required **Important:** The document file must contain the anchor text (e.g., `{SIGNATURE_ALICE}`, `{DATE_ALICE}`) that you reference in your fields. If the anchors don't exist in the document, the API will return an error. **Alternative:** Use a TurboDocx template with pre-configured anchors: ```typescript const result = await TurboSign.sendSignature({ templateId: "template-uuid-from-turbodocx", // Template already contains anchors recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", recipientEmail: "alice@example.com", template: { anchor: "{SIGNATURE_ALICE}", placement: "replace", size: { width: 200, height: 50 }, }, }, ], }); ``` ::: --- ## File Input Methods TurboSign supports four different ways to provide document files: ### 1. File Upload (Buffer/Blob) ```javascript const { readFileSync } = require("fs"); const { TurboSign } = require("@turbodocx/sdk"); const fileBuffer = readFileSync("./contract.pdf"); (async () => { const result = await TurboSign.sendSignature({ file: fileBuffer, recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); })(); ``` ```typescript const fileBuffer = readFileSync("./contract.pdf"); const result = await TurboSign.sendSignature({ file: fileBuffer, recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` ### 2. File URL (fileLink) ```javascript const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` ```typescript const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` :::tip When to use fileLink Use `fileLink` when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files. ::: ### 3. TurboDocx Deliverable ID ```javascript // Use a previously generated TurboDocx document const result = await TurboSign.sendSignature({ deliverableId: "deliverable-uuid-from-turbodocx", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` ```typescript // Use a previously generated TurboDocx document const result = await TurboSign.sendSignature({ deliverableId: "deliverable-uuid-from-turbodocx", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` :::info Integration with TurboDocx `deliverableId` references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign. ::: ### 4. TurboDocx Template ID ```javascript // Use a pre-configured TurboSign template const result = await TurboSign.sendSignature({ templateId: "template-uuid-from-turbodocx", // Template already contains anchors recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", recipientEmail: "alice@example.com", template: { anchor: "{SIGNATURE_ALICE}", placement: "replace", size: { width: 200, height: 50 }, }, }, ], }); ``` ```typescript // Use a pre-configured TurboSign template const result = await TurboSign.sendSignature({ templateId: "template-uuid-from-turbodocx", // Template already contains anchors recipients: [ { name: "Alice Smith", email: "alice@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", recipientEmail: "alice@example.com", template: { anchor: "{SIGNATURE_ALICE}", placement: "replace", size: { width: 200, height: 50 }, }, }, ], }); ``` :::info Integration with TurboDocx `templateId` references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents. ::: --- ## API Reference ### Configure Configure the SDK with your API credentials and organization settings. ```typescript TurboSign.configure({ apiKey: string; // Required : Your TurboDocx API key orgId: string; // Required: Your organization ID senderEmail: string; // Required: reply-to address for signature request emails senderName?: string; // Optional: sender name in emails (default: 'API Service User') baseUrl?: string; // Optional: API base URL (default: 'https://api.turbodocx.com') }); ``` **Example:** ```javascript const { TurboSign } = require("@turbodocx/sdk"); TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // Required for TurboSign // Optional: override for testing // baseUrl: 'https://api.turbodocx.com' }); ``` ```typescript TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY || "", orgId: process.env.TURBODOCX_ORG_ID || "", senderEmail: process.env.TURBODOCX_SENDER_EMAIL || "", // Required for TurboSign // Optional: override for testing // baseUrl: 'https://api.turbodocx.com' }); ``` :::warning API Credentials Required Both `apiKey` and `orgId` parameters are **required** for all API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: ### Prepare for review Upload a document for preview without sending signature request emails. ```javascript const { documentId, previewUrl } = await TurboSign.createSignatureReviewLink({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", documentName: "Contract Draft", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` ```typescript const { documentId, previewUrl } = await TurboSign.createSignatureReviewLink({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", documentName: "Contract Draft", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); ``` ### Prepare for signing Upload a document and immediately send signature requests to all recipients. ```javascript const { documentId } = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", documentName: "Service Agreement", senderName: "Your Company", senderEmail: "sender@company.com", recipients: [ { name: "Recipient Name", email: "recipient@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "recipient@example.com", }, ], }); ``` ```typescript const { documentId } = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", documentName: "Service Agreement", senderName: "Your Company", senderEmail: "sender@company.com", recipients: [ { name: "Recipient Name", email: "recipient@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "recipient@example.com", }, ], }); ``` ### Get status Retrieve the current status of a document. ```javascript const result = await TurboSign.getStatus("document-uuid"); console.log(JSON.stringify(result, null, 2)); ``` ```typescript const result = await TurboSign.getStatus("document-uuid"); console.log(JSON.stringify(result, null, 2)); ``` ### Download document Download the completed signed document as a PDF Blob. ```javascript const { writeFileSync } = require("fs"); (async () => { const result = await TurboSign.download("document-uuid"); // Node.js: Save to file const buffer = Buffer.from(await result.arrayBuffer()); writeFileSync("signed-contract.pdf", buffer); })(); ``` ```typescript const result = await TurboSign.download("document-uuid"); // Node.js: Save to file const buffer = Buffer.from(await result.arrayBuffer()); writeFileSync("signed-contract.pdf", buffer); ``` ### Void Cancel/void a signature request. ```javascript await TurboSign.void("document-uuid", "Contract terms changed"); ``` ```typescript await TurboSign.void("document-uuid", "Contract terms changed"); ``` ### Resend Resend signature request emails to specific recipients. ```javascript // Resend to specific recipients await TurboSign.resend("document-uuid", [ "recipient-uuid-1", "recipient-uuid-2", ]); ``` ```typescript // Resend to specific recipients await TurboSign.resend("document-uuid", [ "recipient-uuid-1", "recipient-uuid-2", ]); ``` ### Get audit trail Retrieve the complete audit trail for a document, including all events and actions. ```javascript const result = await TurboSign.getAuditTrail("document-uuid"); console.log(JSON.stringify(result, null, 2)); ``` ```typescript const result = await TurboSign.getAuditTrail("document-uuid"); console.log(JSON.stringify(result, null, 2)); ``` --- ## Error Handling The SDK provides typed error classes for different failure scenarios. All errors extend the base `TurboDocxError` class. ### Error Classes | Error Class | Status Code | Code | Description | | --------------------- | ----------- | ---------------------- | ----------------------------------- | | `TurboDocxError` | varies | varies | Base error class for all SDK errors | | `AuthenticationError` | 401 | `AUTHENTICATION_ERROR` | Invalid or missing API credentials | | `AuthorizationError` | 403 | `AUTHORIZATION_ERROR` | API key lacks required permissions | | `ValidationError` | 400 | `VALIDATION_ERROR` | Invalid request parameters | | `NotFoundError` | 404 | `NOT_FOUND` | Document or resource not found | | `ConflictError` | 409 | `CONFLICT` | Resource conflict | | `RateLimitError` | 429 | `RATE_LIMIT_EXCEEDED` | Too many requests | | `NetworkError` | - | `NETWORK_ERROR` | Network connectivity issues | ### Handling Errors ```javascript const { TurboSign, TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, } = require("@turbodocx/sdk"); (async () => { try { const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); } catch (error) { if (error instanceof AuthenticationError) { console.error("Authentication failed:", error.message); // Check your API key and org ID } else if (error instanceof ValidationError) { console.error("Validation error:", error.message); // Check request parameters } else if (error instanceof NotFoundError) { console.error("Resource not found:", error.message); // Document or recipient doesn't exist } else if (error instanceof RateLimitError) { console.error("Rate limited:", error.message); // Wait and retry } else if (error instanceof NetworkError) { console.error("Network error:", error.message); // Check connectivity } else if (error instanceof TurboDocxError) { console.error("SDK error:", error.message, error.statusCode, error.code); } } })(); ``` ```typescript TurboSign, TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, } from "@turbodocx/sdk"; try { const result = await TurboSign.sendSignature({ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf", recipients: [ { name: "John Doe", email: "john@example.com", signingOrder: 1 }, ], fields: [ { type: "signature", page: 1, x: 100, y: 650, width: 200, height: 50, recipientEmail: "john@example.com", }, ], }); } catch (error) { if (error instanceof AuthenticationError) { console.error("Authentication failed:", error.message); // Check your API key and org ID } else if (error instanceof ValidationError) { console.error("Validation error:", error.message); // Check request parameters } else if (error instanceof NotFoundError) { console.error("Resource not found:", error.message); // Document or recipient doesn't exist } else if (error instanceof RateLimitError) { console.error("Rate limited:", error.message); // Wait and retry } else if (error instanceof NetworkError) { console.error("Network error:", error.message); // Check connectivity } else if (error instanceof TurboDocxError) { console.error("SDK error:", error.message, error.statusCode, error.code); } } ``` ### Error Properties All errors include these properties: | Property | Type | Description | | ------------ | --------------------- | -------------------------------- | | `message` | `string` | Human-readable error description | | `statusCode` | `number \| undefined` | HTTP status code (if applicable) | | `code` | `string \| undefined` | Machine-readable error code | --- ## TypeScript Types The SDK exports TypeScript types for full type safety. Import them directly from the package. ### Importing Types ```typescript // Field types SignatureFieldType, Field, Recipient, // Request types CreateSignatureReviewLinkRequest, SendSignatureRequest, } from "@turbodocx/sdk"; ``` ### SignatureFieldType Union type for all available field types: ```typescript type SignatureFieldType = | "signature" | "initial" | "date" | "text" | "full_name" | "title" | "company" | "first_name" | "last_name" | "email" | "checkbox"; ``` ### Recipient Recipient configuration for signature requests: | Property | Type | Required | Description | | -------------- | -------- | -------- | ------------------------- | | `name` | `string` | Yes | Recipient's full name | | `email` | `string` | Yes | Recipient's email address | | `signingOrder` | `number` | Yes | Signing order (1-indexed) | ### Field Field configuration supporting both coordinate-based and template-based positioning: | Property | Type | Required | Description | | ----------------- | -------------------- | -------- | --------------------------------------------------- | | `type` | `SignatureFieldType` | Yes | Field type | | `recipientEmail` | `string` | Yes | Which recipient fills this field | | `page` | `number` | No\* | Page number (1-indexed) | | `x` | `number` | No\* | X coordinate in pixels | | `y` | `number` | No\* | Y coordinate in pixels | | `width` | `number` | No\* | Field width in pixels | | `height` | `number` | No\* | Field height in pixels | | `defaultValue` | `string` | No | Default value (for checkbox: `"true"` or `"false"`) | | `isMultiline` | `boolean` | No | Enable multiline text | | `isReadonly` | `boolean` | No | Make field read-only (pre-filled) | | `required` | `boolean` | No | Whether field is required | | `backgroundColor` | `string` | No | Background color (hex, rgb, or named) | | `template` | `object` | No | Template anchor configuration | \*Required when not using template anchors **Template Configuration:** | Property | Type | Required | Description | | --------------- | --------- | -------- | ---------------------------------------------------------------- | | `anchor` | `string` | No† | Text anchor pattern like `{TagName}` | | `placement` | `string` | No | `"replace"` \| `"before"` \| `"after"` \| `"above"` \| `"below"` | | `size` | `object` | No | `{ width: number; height: number }` | | `offset` | `object` | No | `{ x: number; y: number }` | | `caseSensitive` | `boolean` | No | Case sensitive search (default: false) | | `useRegex` | `boolean` | No | Use regex for anchor/searchText (default: false) | †All template properties are optional in the type definition, but at least one of `anchor` or `searchText` must be provided for anchor-based positioning to work. ### CreateSignatureReviewLinkRequest / SendSignatureRequest Request configuration for `createSignatureReviewLink` and `sendSignature` methods: | Property | Type | Required | Description | | --------------------- | ------------- | ----------- | ------------------------------ | | `file` | `Buffer` | Conditional | PDF file as Buffer | | `fileLink` | `string` | Conditional | URL to document file | | `deliverableId` | `string` | Conditional | TurboDocx deliverable ID | | `templateId` | `string` | Conditional | TurboDocx template ID | | `recipients` | `Recipient[]` | Yes | Recipients who will sign | | `fields` | `Field[]` | Yes | Signature fields configuration | | `documentName` | `string` | No | Document name | | `documentDescription` | `string` | No | Document description | | `senderName` | `string` | No | Sender name | | `senderEmail` | `string` | No | Sender email | | `ccEmails` | `string[]` | No | Array of CC email addresses | :::info File Source (Conditional) Exactly one file source is required: `file`, `fileLink`, `deliverableId`, or `templateId`. ::: --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[Request Body Reference](/docs/TurboSign/API%20Signatures#request-body-multipartform-data)** - Complete request body parameters, file sources, and multipart/form-data structure - **[Recipients Reference](/docs/TurboSign/API%20Signatures#recipients-reference)** - Recipient properties, signing order, metadata, and configuration options - **[Field Types Reference](/docs/TurboSign/API%20Signatures#field-types-reference)** - All available field types (signature, date, text, checkbox, etc.) with properties and behaviors - **[Field Positioning Methods](/docs/TurboSign/API%20Signatures#field-positioning-methods)** - Template-based vs coordinate-based positioning, anchor configuration, and best practices --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) - [npm Package](https://www.npmjs.com/package/@turbodocx/sdk) - [API Reference](/docs/TurboSign/API%20Signatures) - [Webhook Configuration](/docs/TurboSign/Webhooks) --- # TurboPartner Go SDK :::tip Interested in TurboPartner? TurboPartner is available for integrators and partners. [Contact us](https://www.turbodocx.com/demo) to get started. ::: The official TurboDocx Partner SDK for Go applications. Build multi-tenant SaaS applications with programmatic organization management, user provisioning, API key management, and entitlement control. Zero dependencies — standard library only. :::info What is TurboPartner? TurboPartner is the partner management API for TurboDocx. It allows you to programmatically create and manage organizations, users, API keys, and feature entitlements — perfect for building white-label or multi-tenant applications on top of TurboDocx. ::: ## TLDR ```go package main "context" "fmt" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { // 1. Configure partner, _ := turbodocx.NewPartnerClient(turbodocx.PartnerConfig{ PartnerAPIKey: os.Getenv("TURBODOCX_PARTNER_API_KEY"), PartnerID: os.Getenv("TURBODOCX_PARTNER_ID"), }) ctx := context.Background() // 2. Create an organization with entitlements org, _ := partner.CreateOrganization(ctx, &turbodocx.CreateOrganizationRequest{ Name: "Acme Corporation", Features: &turbodocx.Features{ MaxUsers: turbodocx.IntPtr(25), // Max users allowed MaxStorage: turbodocx.Int64Ptr(5 * 1024 * 1024 * 1024), // 5GB in bytes HasTDAI: turbodocx.BoolPtr(true), // Enable TurboDocx AI }, }) orgID := org.Data.ID // 3. Add a user user, _ := partner.AddUserToOrganization(ctx, orgID, &turbodocx.AddOrgUserRequest{ Email: "admin@acme.com", Role: "admin", }) fmt.Printf("User: %s\n", user.Data.Email) // 4. Create an API key key, _ := partner.CreateOrganizationAPIKey(ctx, orgID, &turbodocx.CreateOrgAPIKeyRequest{ Name: "Production Key", Role: "admin", }) fmt.Printf("API Key: %s\n", key.Data.Key) // Save this — only shown once! } ``` --- ## Installation ```bash go get github.com/TurboDocx/SDK/packages/go-sdk ``` ## Requirements - Go 1.21 or higher - No external dependencies (standard library only) :::tip Zero Dependencies The Go SDK uses only the standard library — no third-party packages required. This makes it easy to integrate into any Go project. ::: --- ## Configuration ```go // Configure with your partner credentials partner, err := turbodocx.NewPartnerClient(turbodocx.PartnerConfig{ PartnerAPIKey: os.Getenv("TURBODOCX_PARTNER_API_KEY"), // Required: Must start with TDXP- PartnerID: os.Getenv("TURBODOCX_PARTNER_ID"), // Required: Your partner UUID }) if err != nil { log.Fatal(err) } ``` ```go // Auto-configure from environment variables // Leave fields empty to read from TURBODOCX_PARTNER_API_KEY and TURBODOCX_PARTNER_ID partner, err := turbodocx.NewPartnerClient(turbodocx.PartnerConfig{}) if err != nil { log.Fatal(err) } ``` :::caution Partner API Key Required Partner API keys start with `TDXP-` prefix. These are different from regular organization API keys and provide access to partner-level operations across all your organizations. ::: ### Environment Variables ```bash # .env or shell environment export TURBODOCX_PARTNER_API_KEY=TDXP-your-partner-api-key export TURBODOCX_PARTNER_ID=your-partner-uuid ``` --- ## Quick Start ### Create Your First Organization ```go partner, err := turbodocx.NewPartnerClient(turbodocx.PartnerConfig{}) if err != nil { log.Fatal(err) } ctx := context.Background() // Create a new organization result, err := partner.CreateOrganization(ctx, &turbodocx.CreateOrganizationRequest{ Name: "Acme Corporation", }) if err != nil { log.Fatal(err) } fmt.Printf("Organization created!\n") fmt.Printf(" ID: %s\n", result.Data.ID) fmt.Printf(" Name: %s\n", result.Data.Name) ``` :::caution Always Handle Errors The above examples omit error handling for brevity. In production, always check the `err` return value. See [Error Handling](#error-handling) for complete patterns. ::: --- ## Organization Management ### `CreateOrganization()` Create a new organization under your partner account. ```go result, err := partner.CreateOrganization(ctx, &turbodocx.CreateOrganizationRequest{ Name: "Acme Corporation", Features: &turbodocx.Features{ MaxUsers: turbodocx.IntPtr(50), // Optional entitlements override }, }) fmt.Printf("Organization ID: %s\n", result.Data.ID) ``` ### `ListOrganizations()` List all organizations with pagination and search. ```go result, err := partner.ListOrganizations(ctx, &turbodocx.ListOrganizationsRequest{ Limit: turbodocx.IntPtr(25), Offset: turbodocx.IntPtr(0), Search: "Acme", // Optional search by name }) fmt.Printf("Total: %d\n", result.Data.TotalRecords) for _, org := range result.Data.Results { fmt.Printf("- %s (ID: %s)\n", org.Name, org.ID) } ``` ### `GetOrganizationDetails()` Get full details including features and tracking for an organization. ```go result, err := partner.GetOrganizationDetails(ctx, "org-uuid-here") fmt.Printf("Name: %s\n", result.Data.Name) fmt.Printf("Active: %t\n", result.Data.IsActive) if result.Data.Features != nil { if result.Data.Features.MaxUsers != nil { fmt.Printf("Max Users: %d\n", *result.Data.Features.MaxUsers) } if result.Data.Features.MaxStorage != nil { fmt.Printf("Max Storage: %d bytes\n", *result.Data.Features.MaxStorage) } } if result.Data.Tracking != nil { fmt.Printf("Current Users: %d\n", result.Data.Tracking.NumUsers) fmt.Printf("Storage Used: %d bytes\n", result.Data.Tracking.StorageUsed) } ``` ### `UpdateOrganizationInfo()` Update an organization's name. ```go result, err := partner.UpdateOrganizationInfo(ctx, "org-uuid-here", &turbodocx.UpdateOrganizationRequest{ Name: "Acme Corp (Updated)", }, ) ``` ### `UpdateOrganizationEntitlements()` Update an organization's feature limits and capabilities. ```go result, err := partner.UpdateOrganizationEntitlements(ctx, "org-uuid-here", &turbodocx.UpdateEntitlementsRequest{ Features: &turbodocx.Features{ MaxUsers: turbodocx.IntPtr(100), MaxStorage: turbodocx.Int64Ptr(10 * 1024 * 1024 * 1024), // 10GB MaxSignatures: turbodocx.IntPtr(500), HasTDAI: turbodocx.BoolPtr(true), HasFileDownload: turbodocx.BoolPtr(true), HasBetaFeatures: turbodocx.BoolPtr(false), }, }, ) fmt.Println("Entitlements updated!") ``` :::info Features vs Tracking **Features** are limits and capabilities you can set (MaxUsers, HasTDAI, etc.). **Tracking** is read-only usage data (NumUsers, StorageUsed, etc.). See [Entitlements Reference](#entitlements-reference) for all available fields. ::: ### `DeleteOrganization()` Delete an organization (soft delete). ```go result, err := partner.DeleteOrganization(ctx, "org-uuid-here") fmt.Printf("Success: %t\n", result.Success) ``` :::danger Use With Caution Deleting an organization is a destructive operation. All organization data, users, and API keys will be affected. ::: --- ## Organization User Management ### `AddUserToOrganization()` Add a user to an organization with a specific role. ```go result, err := partner.AddUserToOrganization(ctx, "org-uuid-here", &turbodocx.AddOrgUserRequest{ Email: "user@example.com", Role: "admin", // admin, contributor, user, or viewer }, ) fmt.Printf("User ID: %s\n", result.Data.ID) fmt.Printf("Invitation sent to: %s\n", result.Data.Email) ``` ### `ListOrganizationUsers()` List all users in an organization. ```go result, err := partner.ListOrganizationUsers(ctx, "org-uuid-here", &turbodocx.ListOrgUsersRequest{ Limit: turbodocx.IntPtr(50), Offset: turbodocx.IntPtr(0), }, ) fmt.Printf("Total Users: %d\n", result.Data.TotalRecords) for _, user := range result.Data.Results { fmt.Printf("- %s (%s)\n", user.Email, user.Role) } ``` ### `UpdateOrganizationUserRole()` Change a user's role within an organization. ```go result, err := partner.UpdateOrganizationUserRole(ctx, "org-uuid-here", "user-uuid-here", &turbodocx.UpdateOrgUserRequest{Role: "contributor"}, ) ``` ### `ResendOrganizationInvitationToUser()` Resend the invitation email to a pending user. ```go result, err := partner.ResendOrganizationInvitationToUser(ctx, "org-uuid-here", "user-uuid-here", ) ``` ### `RemoveUserFromOrganization()` Remove a user from an organization. ```go result, err := partner.RemoveUserFromOrganization(ctx, "org-uuid-here", "user-uuid-here", ) ``` --- ## Organization API Key Management ### `CreateOrganizationAPIKey()` Create an API key for an organization. ```go result, err := partner.CreateOrganizationAPIKey(ctx, "org-uuid-here", &turbodocx.CreateOrgAPIKeyRequest{ Name: "Production API Key", Role: "admin", // admin, contributor, or viewer }, ) fmt.Printf("Key ID: %s\n", result.Data.ID) fmt.Printf("Full Key: %s\n", result.Data.Key) // Only shown once! ``` :::caution Save Your API Key The full API key is only returned once during creation. Store it securely — you won't be able to retrieve it again. ::: ### `ListOrganizationAPIKeys()` List all API keys for an organization. ```go result, err := partner.ListOrganizationAPIKeys(ctx, "org-uuid-here", &turbodocx.ListOrgAPIKeysRequest{Limit: turbodocx.IntPtr(50)}, ) for _, key := range result.Data.Results { fmt.Printf("- %s (Role: %s)\n", key.Name, key.Role) } ``` ### `UpdateOrganizationAPIKey()` Update an organization API key's name or role. ```go result, err := partner.UpdateOrganizationAPIKey(ctx, "org-uuid-here", "api-key-uuid-here", &turbodocx.UpdateOrgAPIKeyRequest{ Name: "Updated Key Name", Role: "contributor", }, ) ``` ### `RevokeOrganizationAPIKey()` Revoke (delete) an organization API key. ```go result, err := partner.RevokeOrganizationAPIKey(ctx, "org-uuid-here", "api-key-uuid-here", ) ``` --- ## Partner API Key Management ### `CreatePartnerAPIKey()` Create a new partner-level API key with specific scopes. ```go result, err := partner.CreatePartnerAPIKey(ctx, &turbodocx.CreatePartnerAPIKeyRequest{ Name: "Integration API Key", Scopes: []string{ turbodocx.ScopeOrgCreate, turbodocx.ScopeOrgRead, turbodocx.ScopeOrgUpdate, turbodocx.ScopeEntitlementsUpdate, turbodocx.ScopeAuditRead, }, Description: "For third-party integration", }, ) fmt.Printf("Key ID: %s\n", result.Data.ID) fmt.Printf("Full Key: %s\n", result.Data.Key) // Only shown once! ``` ### `ListPartnerAPIKeys()` List all partner API keys. ```go result, err := partner.ListPartnerAPIKeys(ctx, &turbodocx.ListPartnerAPIKeysRequest{Limit: turbodocx.IntPtr(50)}, ) for _, key := range result.Data.Results { fmt.Printf("- %s\n", key.Name) fmt.Printf(" Scopes: %v\n", key.Scopes) } ``` ### `UpdatePartnerAPIKey()` Update a partner API key. ```go result, err := partner.UpdatePartnerAPIKey(ctx, "partner-key-uuid-here", &turbodocx.UpdatePartnerAPIKeyRequest{ Name: "Updated Integration Key", Description: "Updated description", }, ) ``` ### `RevokePartnerAPIKey()` Revoke a partner API key. ```go result, err := partner.RevokePartnerAPIKey(ctx, "partner-key-uuid-here") ``` --- ## Partner User Management ### `AddUserToPartnerPortal()` Add a user to the partner portal with specific permissions. ```go result, err := partner.AddUserToPartnerPortal(ctx, &turbodocx.AddPartnerUserRequest{ Email: "admin@partner.com", Role: "admin", // admin, member, or viewer Permissions: turbodocx.PartnerPermissions{ CanManageOrgs: true, CanManageOrgUsers: true, CanManagePartnerUsers: false, CanManageOrgAPIKeys: true, CanManagePartnerAPIKeys: false, CanUpdateEntitlements: true, CanViewAuditLogs: true, }, }, ) fmt.Printf("Partner User ID: %s\n", result.Data.ID) ``` ### `ListPartnerPortalUsers()` List all partner portal users. ```go result, err := partner.ListPartnerPortalUsers(ctx, &turbodocx.ListPartnerUsersRequest{Limit: turbodocx.IntPtr(50)}, ) for _, user := range result.Data.Results { fmt.Printf("- %s (Role: %s)\n", user.Email, user.Role) } ``` ### `UpdatePartnerUserPermissions()` Update a partner user's role and permissions. ```go result, err := partner.UpdatePartnerUserPermissions(ctx, "partner-user-uuid-here", &turbodocx.UpdatePartnerUserRequest{ Role: "admin", Permissions: &turbodocx.PartnerPermissions{ CanManageOrgs: true, CanManageOrgUsers: true, CanManagePartnerUsers: true, CanManageOrgAPIKeys: true, CanManagePartnerAPIKeys: true, CanUpdateEntitlements: true, CanViewAuditLogs: true, }, }, ) ``` ### `ResendPartnerPortalInvitationToUser()` Resend the invitation email to a pending partner user. ```go result, err := partner.ResendPartnerPortalInvitationToUser(ctx, "partner-user-uuid-here") ``` ### `RemoveUserFromPartnerPortal()` Remove a user from the partner portal. ```go result, err := partner.RemoveUserFromPartnerPortal(ctx, "partner-user-uuid-here") ``` --- ## Audit Logs ### `GetPartnerAuditLogs()` Get audit logs for all partner activities with filtering. ```go result, err := partner.GetPartnerAuditLogs(ctx, &turbodocx.ListAuditLogsRequest{ Limit: turbodocx.IntPtr(50), Offset: turbodocx.IntPtr(0), Action: "ORG_CREATED", // Optional filter by action ResourceType: "organization", // Optional filter by resource type Success: turbodocx.BoolPtr(true), // Optional filter by success/failure StartDate: "2024-01-01", // Optional date range start EndDate: "2024-12-31", // Optional date range end }, ) for _, entry := range result.Data.Results { fmt.Printf("%s - %s", entry.CreatedOn, entry.Action) if entry.ResourceType != "" { fmt.Printf(" (%s)", entry.ResourceType) } if entry.Success { fmt.Println(" - Success") } else { fmt.Println(" - Failed") } } ``` --- ## Entitlements Reference ### Features (Settable Limits) These are limits and capabilities you can configure for each organization: | Field | Type | Description | |-------|------|-------------| | `MaxUsers` | `*int` | Maximum users allowed (-1 = unlimited) | | `MaxProjectspaces` | `*int` | Maximum projectspaces | | `MaxTemplates` | `*int` | Maximum templates | | `MaxStorage` | `*int64` | Maximum storage in bytes | | `MaxGeneratedDeliverables` | `*int` | Maximum generated documents | | `MaxSignatures` | `*int` | Maximum e-signatures | | `MaxAICredits` | `*int` | Maximum AI credits | | `RdWatermark` | `*bool` | Enable RapidDocx watermark | | `HasFileDownload` | `*bool` | Enable file download | | `HasAdvancedDateFormats` | `*bool` | Enable advanced date formats | | `HasGDrive` | `*bool` | Enable Google Drive integration | | `HasSharepoint` | `*bool` | Enable SharePoint integration | | `HasSharepointOnly` | `*bool` | SharePoint-only mode | | `HasTDAI` | `*bool` | Enable TurboDocx AI features | | `HasPptx` | `*bool` | Enable PowerPoint support | | `HasTDWriter` | `*bool` | Enable TurboDocx Writer | | `HasSalesforce` | `*bool` | Enable Salesforce integration | | `HasWrike` | `*bool` | Enable Wrike integration | | `HasVariableStack` | `*bool` | Enable variable stack | | `HasSubvariables` | `*bool` | Enable subvariables | | `HasZapier` | `*bool` | Enable Zapier integration | | `HasBYOM` | `*bool` | Enable Bring Your Own Model | | `HasBYOVS` | `*bool` | Enable Bring Your Own Vector Store | | `HasBetaFeatures` | `*bool` | Enable beta features | | `EnableBulkSending` | `*bool` | Enable bulk document sending | :::tip Pointer Helpers Use the provided helper functions for setting optional fields: - `turbodocx.IntPtr(25)` — for `*int` fields - `turbodocx.Int64Ptr(5368709120)` — for `*int64` fields (storage) - `turbodocx.BoolPtr(true)` — for `*bool` fields ::: ### Tracking (Read-Only Usage) These are usage counters that are read-only: | Field | Type | Description | |-------|------|-------------| | `NumUsers` | `int` | Current number of users | | `NumProjectspaces` | `int` | Current number of projectspaces | | `NumTemplates` | `int` | Current number of templates | | `StorageUsed` | `int64` | Current storage used in bytes | | `NumGeneratedDeliverables` | `int` | Total documents generated | | `NumSignaturesUsed` | `int` | Total signatures used | | `CurrentAICredits` | `int` | Remaining AI credits | --- ## Scope Constants ### Partner Scopes (22 Scopes) ```go // Organization CRUD turbodocx.ScopeOrgCreate // "org:create" turbodocx.ScopeOrgRead // "org:read" turbodocx.ScopeOrgUpdate // "org:update" turbodocx.ScopeOrgDelete // "org:delete" // Entitlements turbodocx.ScopeEntitlementsUpdate // "entitlements:update" // Organization Users turbodocx.ScopeOrgUsersCreate // "org-users:create" turbodocx.ScopeOrgUsersRead // "org-users:read" turbodocx.ScopeOrgUsersUpdate // "org-users:update" turbodocx.ScopeOrgUsersDelete // "org-users:delete" // Partner Users turbodocx.ScopePartnerUsersCreate // "partner-users:create" turbodocx.ScopePartnerUsersRead // "partner-users:read" turbodocx.ScopePartnerUsersUpdate // "partner-users:update" turbodocx.ScopePartnerUsersDelete // "partner-users:delete" // Organization API Keys turbodocx.ScopeOrgApikeysCreate // "org-apikeys:create" turbodocx.ScopeOrgApikeysRead // "org-apikeys:read" turbodocx.ScopeOrgApikeysUpdate // "org-apikeys:update" turbodocx.ScopeOrgApikeysDelete // "org-apikeys:delete" // Partner API Keys turbodocx.ScopePartnerApikeysCreate // "partner-apikeys:create" turbodocx.ScopePartnerApikeysRead // "partner-apikeys:read" turbodocx.ScopePartnerApikeysUpdate // "partner-apikeys:update" turbodocx.ScopePartnerApikeysDelete // "partner-apikeys:delete" // Audit turbodocx.ScopeAuditRead // "audit:read" ``` ### Organization User Roles | Role | Description | |------|-------------| | `"admin"` | Full organization access | | `"contributor"` | Can create and edit content | | `"user"` | Standard user access | | `"viewer"` | Read-only access | ### Partner User Roles | Role | Description | |------|-------------| | `"admin"` | Full partner portal access | | `"member"` | Standard partner access (respects permissions) | | `"viewer"` | Read-only access to partner portal | ### PartnerPermissions ```go permissions := turbodocx.PartnerPermissions{ CanManageOrgs: true, // Create, update, delete organizations CanManageOrgUsers: true, // Manage users within organizations CanManagePartnerUsers: false, // Manage other partner portal users CanManageOrgAPIKeys: true, // Manage organization API keys CanManagePartnerAPIKeys: false, // Manage partner API keys CanUpdateEntitlements: true, // Update organization entitlements CanViewAuditLogs: true, // View audit logs } ``` --- ## Error Handling The SDK provides typed errors for different error scenarios: ```go result, err := partner.CreateOrganization(ctx, request) if err != nil { var authErr *turbodocx.AuthenticationError var validErr *turbodocx.ValidationError var notFoundErr *turbodocx.NotFoundError var rateLimitErr *turbodocx.RateLimitError var networkErr *turbodocx.NetworkError switch { case errors.As(err, &authErr): // 401 - Invalid API key or partner ID fmt.Printf("Authentication failed: %s\n", authErr.Message) case errors.As(err, &validErr): // 400 - Invalid request data fmt.Printf("Validation error: %s\n", validErr.Message) case errors.As(err, ¬FoundErr): // 404 - Organization or resource not found fmt.Printf("Not found: %s\n", notFoundErr.Message) case errors.As(err, &rateLimitErr): // 429 - Rate limit exceeded fmt.Printf("Rate limit: %s\n", rateLimitErr.Message) case errors.As(err, &networkErr): // Network/connection error fmt.Printf("Network error: %s\n", networkErr.Message) default: fmt.Printf("Error: %v\n", err) } } ``` ### Error Types | Error Type | Status Code | Description | |------------|-------------|-------------| | `TurboDocxError` | varies | Base error for all SDK errors | | `AuthenticationError` | 401 | Invalid or missing API credentials | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Resource not found | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | --- ## Complete Example Here's a complete example showing a typical partner workflow: ```go package main "context" "fmt" "log" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { // Configure partner, err := turbodocx.NewPartnerClient(turbodocx.PartnerConfig{}) if err != nil { log.Fatal(err) } ctx := context.Background() // 1. Create an organization for a new customer org, err := partner.CreateOrganization(ctx, &turbodocx.CreateOrganizationRequest{ Name: "New Customer Inc", }) if err != nil { log.Fatal(err) } fmt.Printf("Created organization: %s\n", org.Data.ID) // 2. Set up their entitlements based on their plan _, err = partner.UpdateOrganizationEntitlements(ctx, org.Data.ID, &turbodocx.UpdateEntitlementsRequest{ Features: &turbodocx.Features{ MaxUsers: turbodocx.IntPtr(25), MaxStorage: turbodocx.Int64Ptr(5 * 1024 * 1024 * 1024), // 5GB HasTDAI: turbodocx.BoolPtr(true), HasFileDownload: turbodocx.BoolPtr(true), }, }, ) if err != nil { log.Fatal(err) } fmt.Println("Configured entitlements") // 3. Add their admin user user, err := partner.AddUserToOrganization(ctx, org.Data.ID, &turbodocx.AddOrgUserRequest{ Email: "admin@newcustomer.com", Role: "admin", }, ) if err != nil { log.Fatal(err) } fmt.Printf("Added admin user: %s\n", user.Data.Email) // 4. Create an API key for their integration apiKey, err := partner.CreateOrganizationAPIKey(ctx, org.Data.ID, &turbodocx.CreateOrgAPIKeyRequest{ Name: "Production API Key", Role: "admin", }, ) if err != nil { log.Fatal(err) } fmt.Printf("Created API key: %s\n", apiKey.Data.Key) fmt.Println("\nCustomer setup complete!") } ``` --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) - [Go Package Reference](https://pkg.go.dev/github.com/TurboDocx/SDK/packages/go-sdk) - [TurboSign Go SDK](/docs/SDKs/go) — For digital signature operations - [API Reference](/docs/API/partner-api) — REST API documentation --- # TurboPartner JavaScript SDK :::tip Interested in TurboPartner? TurboPartner is available for integrators and partners. [Contact us](https://www.turbodocx.com/demo) to get started. ::: The official TurboDocx Partner SDK for JavaScript and TypeScript applications. Build multi-tenant SaaS applications with programmatic organization management, user provisioning, API key management, and entitlement control. Available on npm as `@turbodocx/sdk`. :::info What is TurboPartner? TurboPartner is the partner management API for TurboDocx. It allows you to programmatically create and manage organizations, users, API keys, and feature entitlements — perfect for building white-label or multi-tenant applications on top of TurboDocx. ::: ## TLDR ```typescript // 1. Configure TurboPartner.configure({ partnerApiKey: process.env.TURBODOCX_PARTNER_API_KEY!, partnerId: process.env.TURBODOCX_PARTNER_ID!, }); // 2. Create an organization with entitlements const org = await TurboPartner.createOrganization({ name: 'Acme Corporation', features: { maxUsers: 25, // Max users allowed maxStorage: 5368709120, // 5GB in bytes hasTDAI: true, // Enable TurboDocx AI }, }); const orgId = org.data.id; // 3. Add a user const user = await TurboPartner.addUserToOrganization(orgId, { email: 'admin@acme.com', role: 'admin', }); // 4. Create an API key const key = await TurboPartner.createOrganizationApiKey(orgId, { name: 'Production Key', role: 'admin', }); console.log(`API Key: ${key.data.key}`); // Save this — only shown once! ``` --- ## Installation ```bash npm install @turbodocx/sdk # or yarn add @turbodocx/sdk # or pnpm add @turbodocx/sdk ``` ## Requirements - Node.js 18 or higher - TypeScript 4.7+ (optional, types included) :::tip Full TypeScript Support This SDK includes complete TypeScript type definitions for all request/response types, enums, and configuration options — no additional `@types` packages needed. ::: --- ## Configuration ```typescript // Configure with your partner credentials TurboPartner.configure({ partnerApiKey: process.env.TURBODOCX_PARTNER_API_KEY, // Required: Must start with TDXP- partnerId: process.env.TURBODOCX_PARTNER_ID, // Required: Your partner UUID }); ``` ```typescript // Auto-configure from environment variables // Reads from: TURBODOCX_PARTNER_API_KEY, TURBODOCX_PARTNER_ID TurboPartner.configure({ partnerApiKey: process.env.TURBODOCX_PARTNER_API_KEY!, partnerId: process.env.TURBODOCX_PARTNER_ID!, }); ``` :::caution Partner API Key Required Partner API keys start with `TDXP-` prefix. These are different from regular organization API keys and provide access to partner-level operations across all your organizations. ::: ### Environment Variables ```bash # .env TURBODOCX_PARTNER_API_KEY=TDXP-your-partner-api-key TURBODOCX_PARTNER_ID=your-partner-uuid ``` --- ## Quick Start ### Create Your First Organization ```typescript // Configure with your partner credentials TurboPartner.configure({ partnerApiKey: process.env.TURBODOCX_PARTNER_API_KEY!, partnerId: process.env.TURBODOCX_PARTNER_ID!, }); // Create a new organization const result = await TurboPartner.createOrganization({ name: 'Acme Corporation', }); console.log('Organization created!'); console.log(` ID: ${result.data.id}`); console.log(` Name: ${result.data.name}`); ``` :::caution Always Handle Errors The above examples omit error handling for brevity. In production, wrap all TurboPartner calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: --- ## Organization Management ### `createOrganization()` Create a new organization under your partner account. ```typescript const result = await TurboPartner.createOrganization({ name: 'Acme Corporation', features: { maxUsers: 50 }, // Optional entitlements override }); console.log(`Organization ID: ${result.data.id}`); ``` ### `listOrganizations()` List all organizations with pagination and search. ```typescript const result = await TurboPartner.listOrganizations({ limit: 25, offset: 0, search: 'Acme', // Optional search by name }); console.log(`Total: ${result.data.totalRecords}`); for (const org of result.data.results) { console.log(`- ${org.name} (ID: ${org.id})`); } ``` ### `getOrganizationDetails()` Get full details including features and tracking for an organization. ```typescript const result = await TurboPartner.getOrganizationDetails('org-uuid-here'); console.log(`Name: ${result.data.name}`); console.log(`Active: ${result.data.isActive ? 'Yes' : 'No'}`); if (result.data.features) { console.log(`Max Users: ${result.data.features.maxUsers}`); console.log(`Max Storage: ${result.data.features.maxStorage} bytes`); } if (result.data.tracking) { console.log(`Current Users: ${result.data.tracking.numUsers}`); console.log(`Storage Used: ${result.data.tracking.storageUsed} bytes`); } ``` ### `updateOrganizationInfo()` Update an organization's name. ```typescript const result = await TurboPartner.updateOrganizationInfo( 'org-uuid-here', { name: 'Acme Corp (Updated)' } ); ``` ### `updateOrganizationEntitlements()` Update an organization's feature limits and capabilities. ```typescript const result = await TurboPartner.updateOrganizationEntitlements( 'org-uuid-here', { features: { maxUsers: 100, maxStorage: 10737418240, // 10GB in bytes maxSignatures: 500, hasTDAI: true, hasFileDownload: true, hasBetaFeatures: false, }, } ); console.log('Entitlements updated!'); ``` :::info Features vs Tracking **Features** are limits and capabilities you can set (maxUsers, hasTDAI, etc.). **Tracking** is read-only usage data (numUsers, storageUsed, etc.). See [Entitlements Reference](#entitlements-reference) for all available fields. ::: ### `deleteOrganization()` Delete an organization (soft delete). ```typescript const result = await TurboPartner.deleteOrganization('org-uuid-here'); console.log(`Success: ${result.success}`); ``` :::danger Use With Caution Deleting an organization is a destructive operation. All organization data, users, and API keys will be affected. ::: --- ## Organization User Management ### `addUserToOrganization()` Add a user to an organization with a specific role. ```typescript const result = await TurboPartner.addUserToOrganization( 'org-uuid-here', { email: 'user@example.com', role: 'admin', // 'admin' | 'contributor' | 'user' | 'viewer' } ); console.log(`User ID: ${result.data.id}`); console.log(`Invitation sent to: ${result.data.email}`); ``` ### `listOrganizationUsers()` List all users in an organization. ```typescript const result = await TurboPartner.listOrganizationUsers( 'org-uuid-here', { limit: 50, offset: 0 } ); console.log(`Total Users: ${result.data.totalRecords}`); for (const user of result.data.results) { console.log(`- ${user.email} (${user.role})`); } ``` ### `updateOrganizationUserRole()` Change a user's role within an organization. ```typescript const result = await TurboPartner.updateOrganizationUserRole( 'org-uuid-here', 'user-uuid-here', { role: 'contributor' } ); ``` ### `resendOrganizationInvitationToUser()` Resend the invitation email to a pending user. ```typescript const result = await TurboPartner.resendOrganizationInvitationToUser( 'org-uuid-here', 'user-uuid-here' ); ``` ### `removeUserFromOrganization()` Remove a user from an organization. ```typescript const result = await TurboPartner.removeUserFromOrganization( 'org-uuid-here', 'user-uuid-here' ); ``` --- ## Organization API Key Management ### `createOrganizationApiKey()` Create an API key for an organization. ```typescript const result = await TurboPartner.createOrganizationApiKey( 'org-uuid-here', { name: 'Production API Key', role: 'admin', // 'admin' | 'contributor' | 'viewer' } ); console.log(`Key ID: ${result.data.id}`); console.log(`Full Key: ${result.data.key}`); // Only shown once! ``` :::caution Save Your API Key The full API key is only returned once during creation. Store it securely — you won't be able to retrieve it again. ::: ### `listOrganizationApiKeys()` List all API keys for an organization. ```typescript const result = await TurboPartner.listOrganizationApiKeys( 'org-uuid-here', { limit: 50 } ); for (const key of result.data.results) { console.log(`- ${key.name} (Role: ${key.role})`); } ``` ### `updateOrganizationApiKey()` Update an organization API key's name or role. ```typescript const result = await TurboPartner.updateOrganizationApiKey( 'org-uuid-here', 'api-key-uuid-here', { name: 'Updated Key Name', role: 'contributor', } ); ``` ### `revokeOrganizationApiKey()` Revoke (delete) an organization API key. ```typescript const result = await TurboPartner.revokeOrganizationApiKey( 'org-uuid-here', 'api-key-uuid-here' ); ``` --- ## Partner API Key Management ### `createPartnerApiKey()` Create a new partner-level API key with specific scopes. ```typescript const result = await TurboPartner.createPartnerApiKey({ name: 'Integration API Key', scopes: [ 'org:create', 'org:read', 'org:update', 'entitlements:update', 'audit:read', ], description: 'For third-party integration', }); console.log(`Key ID: ${result.data.id}`); console.log(`Full Key: ${result.data.key}`); // Only shown once! ``` ### `listPartnerApiKeys()` List all partner API keys. ```typescript const result = await TurboPartner.listPartnerApiKeys({ limit: 50 }); for (const key of result.data.results) { console.log(`- ${key.name}`); console.log(` Scopes: ${key.scopes?.join(', ')}`); } ``` ### `updatePartnerApiKey()` Update a partner API key. ```typescript const result = await TurboPartner.updatePartnerApiKey( 'partner-key-uuid-here', { name: 'Updated Integration Key', description: 'Updated description', } ); ``` ### `revokePartnerApiKey()` Revoke a partner API key. ```typescript const result = await TurboPartner.revokePartnerApiKey('partner-key-uuid-here'); ``` --- ## Partner User Management ### `addUserToPartnerPortal()` Add a user to the partner portal with specific permissions. ```typescript const result = await TurboPartner.addUserToPartnerPortal({ email: 'admin@partner.com', role: 'admin', // 'admin' | 'member' | 'viewer' permissions: { canManageOrgs: true, canManageOrgUsers: true, canManagePartnerUsers: false, canManageOrgAPIKeys: true, canManagePartnerAPIKeys: false, canUpdateEntitlements: true, canViewAuditLogs: true, }, }); console.log(`Partner User ID: ${result.data.id}`); ``` ### `listPartnerPortalUsers()` List all partner portal users. ```typescript const result = await TurboPartner.listPartnerPortalUsers({ limit: 50 }); for (const user of result.data.results) { console.log(`- ${user.email} (Role: ${user.role})`); } ``` ### `updatePartnerUserPermissions()` Update a partner user's role and permissions. ```typescript const result = await TurboPartner.updatePartnerUserPermissions( 'partner-user-uuid-here', { role: 'admin', permissions: { canManageOrgs: true, canManageOrgUsers: true, canManagePartnerUsers: true, canManageOrgAPIKeys: true, canManagePartnerAPIKeys: true, canUpdateEntitlements: true, canViewAuditLogs: true, }, } ); ``` ### `resendPartnerPortalInvitationToUser()` Resend the invitation email to a pending partner user. ```typescript const result = await TurboPartner.resendPartnerPortalInvitationToUser( 'partner-user-uuid-here' ); ``` ### `removeUserFromPartnerPortal()` Remove a user from the partner portal. ```typescript const result = await TurboPartner.removeUserFromPartnerPortal( 'partner-user-uuid-here' ); ``` --- ## Audit Logs ### `getPartnerAuditLogs()` Get audit logs for all partner activities with filtering. ```typescript const result = await TurboPartner.getPartnerAuditLogs({ limit: 50, offset: 0, action: 'org.created', // Optional filter by action resourceType: 'organization', // Optional filter by resource type success: true, // Optional filter by success/failure startDate: '2024-01-01', // Optional date range start endDate: '2024-12-31', // Optional date range end }); for (const entry of result.data.results) { let line = `${entry.createdOn} - ${entry.action}`; if (entry.resourceType) { line += ` (${entry.resourceType})`; } line += ` - ${entry.success ? 'Success' : 'Failed'}`; console.log(line); } ``` --- ## Entitlements Reference ### Features (Settable Limits) These are limits and capabilities you can configure for each organization: | Field | Type | Description | |-------|------|-------------| | `maxUsers` | number | Maximum users allowed (-1 = unlimited) | | `maxProjectspaces` | number | Maximum projectspaces | | `maxTemplates` | number | Maximum templates | | `maxStorage` | number | Maximum storage in bytes | | `maxGeneratedDeliverables` | number | Maximum generated documents | | `maxSignatures` | number | Maximum e-signatures | | `maxAICredits` | number | Maximum AI credits | | `rdWatermark` | boolean | Enable RapidDocx watermark | | `hasFileDownload` | boolean | Enable file download | | `hasAdvancedDateFormats` | boolean | Enable advanced date formats | | `hasGDrive` | boolean | Enable Google Drive integration | | `hasSharepoint` | boolean | Enable SharePoint integration | | `hasSharepointOnly` | boolean | SharePoint-only mode | | `hasTDAI` | boolean | Enable TurboDocx AI features | | `hasPptx` | boolean | Enable PowerPoint support | | `hasTDWriter` | boolean | Enable TurboDocx Writer | | `hasSalesforce` | boolean | Enable Salesforce integration | | `hasWrike` | boolean | Enable Wrike integration | | `hasVariableStack` | boolean | Enable variable stack | | `hasSubvariables` | boolean | Enable subvariables | | `hasZapier` | boolean | Enable Zapier integration | | `hasBYOM` | boolean | Enable Bring Your Own Model | | `hasBYOVS` | boolean | Enable Bring Your Own Vector Store | | `hasBetaFeatures` | boolean | Enable beta features | | `enableBulkSending` | boolean | Enable bulk document sending | ### Tracking (Read-Only Usage) These are usage counters that are read-only: | Field | Type | Description | |-------|------|-------------| | `numUsers` | number | Current number of users | | `numProjectspaces` | number | Current number of projectspaces | | `numTemplates` | number | Current number of templates | | `storageUsed` | number | Current storage used in bytes | | `numGeneratedDeliverables` | number | Total documents generated | | `numSignaturesUsed` | number | Total signatures used | | `currentAICredits` | number | Remaining AI credits | --- ## Types and Interfaces ### PartnerScope (22 Scopes) ```typescript // Organization CRUD 'org:create' 'org:read' 'org:update' 'org:delete' // Entitlements 'entitlements:update' // Organization Users 'org-users:create' 'org-users:read' 'org-users:update' 'org-users:delete' // Partner Users 'partner-users:create' 'partner-users:read' 'partner-users:update' 'partner-users:delete' // Organization API Keys 'org-apikeys:create' 'org-apikeys:read' 'org-apikeys:update' 'org-apikeys:delete' // Partner API Keys 'partner-apikeys:create' 'partner-apikeys:read' 'partner-apikeys:update' 'partner-apikeys:delete' // Audit 'audit:read' ``` ### OrgUserRole (Organization Users) ```typescript type OrgUserRole = 'admin' | 'contributor' | 'user' | 'viewer'; ``` | Role | Description | |------|-------------| | `'admin'` | Full organization access | | `'contributor'` | Can create and edit content | | `'user'` | Standard user access | | `'viewer'` | Read-only access | ### PartnerUserRole (Partner Portal Users) ```typescript type PartnerUserRole = 'admin' | 'member' | 'viewer'; ``` | Role | Description | |------|-------------| | `'admin'` | Full partner portal access | | `'member'` | Standard partner access (respects permissions) | | `'viewer'` | Read-only access to partner portal | ### PartnerPermissions ```typescript interface PartnerPermissions { canManageOrgs: boolean; // Create, update, delete organizations canManageOrgUsers: boolean; // Manage users within organizations canManagePartnerUsers: boolean; // Manage other partner portal users canManageOrgAPIKeys: boolean; // Manage organization API keys canManagePartnerAPIKeys: boolean; // Manage partner API keys canUpdateEntitlements: boolean; // Update organization entitlements canViewAuditLogs: boolean; // View audit logs } ``` --- ## Error Handling The SDK provides typed error classes for different error scenarios: ```typescript TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, } from '@turbodocx/sdk'; try { const result = await TurboPartner.createOrganization({ name: 'Acme Corp' }); } catch (error) { if (error instanceof AuthenticationError) { // 401 - Invalid API key or partner ID console.error(`Authentication failed: ${error.message}`); } else if (error instanceof ValidationError) { // 400 - Invalid request data console.error(`Validation error: ${error.message}`); } else if (error instanceof NotFoundError) { // 404 - Organization or resource not found console.error(`Not found: ${error.message}`); } else if (error instanceof RateLimitError) { // 429 - Rate limit exceeded console.error(`Rate limit: ${error.message}`); } else if (error instanceof NetworkError) { // Network/connection error console.error(`Network error: ${error.message}`); } else if (error instanceof TurboDocxError) { // Other API error console.error(`Error ${error.code}: ${error.message}`); } } ``` ### Error Classes | Error Class | Status Code | Description | |-------------|-------------|-------------| | `TurboDocxError` | varies | Base error for all SDK errors | | `AuthenticationError` | 401 | Invalid or missing API credentials | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Resource not found | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | --- ## Complete Example Here's a complete example showing a typical partner workflow: ```typescript // Configure TurboPartner.configure({ partnerApiKey: process.env.TURBODOCX_PARTNER_API_KEY!, partnerId: process.env.TURBODOCX_PARTNER_ID!, }); try { // 1. Create an organization for a new customer const org = await TurboPartner.createOrganization({ name: 'New Customer Inc', }); console.log(`Created organization: ${org.data.id}`); // 2. Set up their entitlements based on their plan await TurboPartner.updateOrganizationEntitlements( org.data.id, { features: { maxUsers: 25, maxStorage: 5368709120, // 5GB hasTDAI: true, hasFileDownload: true, }, } ); console.log('Configured entitlements'); // 3. Add their admin user const user = await TurboPartner.addUserToOrganization( org.data.id, { email: 'admin@newcustomer.com', role: 'admin', } ); console.log(`Added admin user: ${user.data.email}`); // 4. Create an API key for their integration const apiKey = await TurboPartner.createOrganizationApiKey( org.data.id, { name: 'Production API Key', role: 'admin', } ); console.log(`Created API key: ${apiKey.data.key}`); console.log('\nCustomer setup complete!'); } catch (error) { if (error instanceof TurboDocxError) { console.error(`Error: ${error.message}`); process.exit(1); } throw error; } ``` --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) - [npm Package](https://www.npmjs.com/package/@turbodocx/sdk) - [TurboSign JavaScript SDK](/docs/SDKs/javascript) — For digital signature operations - [API Reference](/docs/API/partner-api) — REST API documentation --- # TurboPartner PHP SDK :::tip Interested in TurboPartner? TurboPartner is available for integrators and partners. [Contact us](https://www.turbodocx.com/demo) to get started. ::: The official TurboDocx Partner SDK for PHP applications. Build multi-tenant SaaS applications with programmatic organization management, user provisioning, API key management, and entitlement control. Available on Packagist as `turbodocx/sdk`. :::info What is TurboPartner? TurboPartner is the partner management API for TurboDocx. It allows you to programmatically create and manage organizations, users, API keys, and feature entitlements — perfect for building white-label or multi-tenant applications on top of TurboDocx. ::: ## TLDR ```php 25, // Max users allowed 'maxStorage' => 5368709120, // 5GB in bytes 'hasTDAI' => true, // Enable TurboDocx AI ] ) ); $orgId = $org->data->id; // 3. Add a user $user = TurboPartner::addUserToOrganization($orgId, new AddOrgUserRequest(email: 'admin@acme.com', role: OrgUserRole::ADMIN) ); // 4. Create an API key $key = TurboPartner::createOrganizationApiKey($orgId, new CreateOrgApiKeyRequest(name: 'Production Key', role: 'admin') ); echo "API Key: {$key->data->key}\n"; // Save this — only shown once! ``` --- ## Installation ```bash composer require turbodocx/sdk ``` ## Requirements - PHP 8.1 or higher - Composer - ext-json - ext-curl :::tip Modern PHP Features This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience. ::: --- ## Configuration ```php ```php :::caution Partner API Key Required Partner API keys start with `TDXP-` prefix. These are different from regular organization API keys and provide access to partner-level operations across all your organizations. ::: ### Environment Variables ```bash # .env TURBODOCX_PARTNER_API_KEY=TDXP-your-partner-api-key TURBODOCX_PARTNER_ID=your-partner-uuid ``` --- ## Quick Start ### Create Your First Organization ```php data->id}\n"; echo " Name: {$result->data->name}\n"; ``` :::caution Always Handle Errors The above examples omit error handling for brevity. In production, wrap all TurboPartner calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: --- ## Organization Management ### `createOrganization()` Create a new organization under your partner account. ```php use TurboDocx\Types\Requests\Partner\CreateOrganizationRequest; $result = TurboPartner::createOrganization( new CreateOrganizationRequest( name: 'Acme Corporation', features: ['maxUsers' => 50] // Optional entitlements override ) ); echo "Organization ID: {$result->data->id}\n"; ``` ### `listOrganizations()` List all organizations with pagination and search. ```php use TurboDocx\Types\Requests\Partner\ListOrganizationsRequest; $result = TurboPartner::listOrganizations( new ListOrganizationsRequest( limit: 25, offset: 0, search: 'Acme' // Optional search by name ) ); echo "Total: {$result->totalRecords}\n"; foreach ($result->results as $org) { echo "- {$org->name} (ID: {$org->id})\n"; } ``` ### `getOrganizationDetails()` Get full details including features and tracking for an organization. ```php $result = TurboPartner::getOrganizationDetails('org-uuid-here'); echo "Name: {$result->organization->name}\n"; echo "Active: " . ($result->organization->isActive ? 'Yes' : 'No') . "\n"; if ($result->features) { echo "Max Users: {$result->features->maxUsers}\n"; echo "Max Storage: {$result->features->maxStorage} bytes\n"; } if ($result->tracking) { echo "Current Users: {$result->tracking->numUsers}\n"; echo "Storage Used: {$result->tracking->storageUsed} bytes\n"; } ``` ### `updateOrganizationInfo()` Update an organization's name. ```php use TurboDocx\Types\Requests\Partner\UpdateOrganizationRequest; $result = TurboPartner::updateOrganizationInfo( 'org-uuid-here', new UpdateOrganizationRequest( name: 'Acme Corp (Updated)' ) ); ``` ### `updateOrganizationEntitlements()` Update an organization's feature limits and capabilities. ```php use TurboDocx\Types\Requests\Partner\UpdateEntitlementsRequest; $result = TurboPartner::updateOrganizationEntitlements( 'org-uuid-here', new UpdateEntitlementsRequest( features: [ 'maxUsers' => 100, 'maxStorage' => 10737418240, // 10GB in bytes 'maxSignatures' => 500, 'hasTDAI' => true, 'hasFileDownload' => true, 'hasBetaFeatures' => false, ] ) ); echo "Entitlements updated!\n"; ``` :::info Features vs Tracking **Features** are limits and capabilities you can set (maxUsers, hasTDAI, etc.). **Tracking** is read-only usage data (numUsers, storageUsed, etc.). See [Entitlements Reference](#entitlements-reference) for all available fields. ::: ### `deleteOrganization()` Delete an organization (soft delete). ```php $result = TurboPartner::deleteOrganization('org-uuid-here'); echo "Success: " . ($result->success ? 'Yes' : 'No') . "\n"; ``` :::danger Use With Caution Deleting an organization is a destructive operation. All organization data, users, and API keys will be affected. ::: --- ## Organization User Management ### `addUserToOrganization()` Add a user to an organization with a specific role. ```php use TurboDocx\Types\Requests\Partner\AddOrgUserRequest; use TurboDocx\Types\Enums\OrgUserRole; $result = TurboPartner::addUserToOrganization( 'org-uuid-here', new AddOrgUserRequest( email: 'user@example.com', role: OrgUserRole::ADMIN // ADMIN, CONTRIBUTOR, USER, or VIEWER ) ); echo "User ID: {$result->data->id}\n"; echo "Invitation sent to: {$result->data->email}\n"; ``` ### `listOrganizationUsers()` List all users in an organization. ```php use TurboDocx\Types\Requests\Partner\ListOrgUsersRequest; $result = TurboPartner::listOrganizationUsers( 'org-uuid-here', new ListOrgUsersRequest(limit: 50, offset: 0) ); echo "Total Users: {$result->totalRecords}\n"; foreach ($result->results as $user) { echo "- {$user->email} ({$user->role})\n"; } ``` ### `updateOrganizationUserRole()` Change a user's role within an organization. ```php use TurboDocx\Types\Requests\Partner\UpdateOrgUserRequest; use TurboDocx\Types\Enums\OrgUserRole; $result = TurboPartner::updateOrganizationUserRole( 'org-uuid-here', 'user-uuid-here', new UpdateOrgUserRequest(role: OrgUserRole::CONTRIBUTOR) ); ``` ### `resendOrganizationInvitationToUser()` Resend the invitation email to a pending user. ```php $result = TurboPartner::resendOrganizationInvitationToUser( 'org-uuid-here', 'user-uuid-here' ); ``` ### `removeUserFromOrganization()` Remove a user from an organization. ```php $result = TurboPartner::removeUserFromOrganization( 'org-uuid-here', 'user-uuid-here' ); ``` --- ## Organization API Key Management ### `createOrganizationApiKey()` Create an API key for an organization. ```php use TurboDocx\Types\Requests\Partner\CreateOrgApiKeyRequest; $result = TurboPartner::createOrganizationApiKey( 'org-uuid-here', new CreateOrgApiKeyRequest( name: 'Production API Key', role: 'admin' // admin, contributor, or viewer ) ); echo "Key ID: {$result->data->id}\n"; echo "Full Key: {$result->data->key}\n"; // Only shown once! ``` :::caution Save Your API Key The full API key is only returned once during creation. Store it securely — you won't be able to retrieve it again. ::: ### `listOrganizationApiKeys()` List all API keys for an organization. ```php use TurboDocx\Types\Requests\Partner\ListOrgApiKeysRequest; $result = TurboPartner::listOrganizationApiKeys( 'org-uuid-here', new ListOrgApiKeysRequest(limit: 50) ); foreach ($result->results as $key) { echo "- {$key->name} (Role: {$key->role})\n"; } ``` ### `updateOrganizationApiKey()` Update an organization API key's name or role. ```php use TurboDocx\Types\Requests\Partner\UpdateOrgApiKeyRequest; $result = TurboPartner::updateOrganizationApiKey( 'org-uuid-here', 'api-key-uuid-here', new UpdateOrgApiKeyRequest( name: 'Updated Key Name', role: 'contributor' ) ); ``` ### `revokeOrganizationApiKey()` Revoke (delete) an organization API key. ```php $result = TurboPartner::revokeOrganizationApiKey( 'org-uuid-here', 'api-key-uuid-here' ); ``` --- ## Partner API Key Management ### `createPartnerApiKey()` Create a new partner-level API key with specific scopes. ```php use TurboDocx\Types\Requests\Partner\CreatePartnerApiKeyRequest; use TurboDocx\Types\Enums\PartnerScope; $result = TurboPartner::createPartnerApiKey( new CreatePartnerApiKeyRequest( name: 'Integration API Key', scopes: [ PartnerScope::ORG_CREATE, PartnerScope::ORG_READ, PartnerScope::ORG_UPDATE, PartnerScope::ENTITLEMENTS_UPDATE, PartnerScope::AUDIT_READ, ], description: 'For third-party integration' ) ); echo "Key ID: {$result->data->id}\n"; echo "Full Key: {$result->data->key}\n"; // Only shown once! ``` ### `listPartnerApiKeys()` List all partner API keys. ```php use TurboDocx\Types\Requests\Partner\ListPartnerApiKeysRequest; $result = TurboPartner::listPartnerApiKeys( new ListPartnerApiKeysRequest(limit: 50) ); foreach ($result->results as $key) { echo "- {$key->name}\n"; echo " Scopes: " . implode(', ', $key->scopes ?? []) . "\n"; } ``` ### `updatePartnerApiKey()` Update a partner API key. ```php use TurboDocx\Types\Requests\Partner\UpdatePartnerApiKeyRequest; $result = TurboPartner::updatePartnerApiKey( 'partner-key-uuid-here', new UpdatePartnerApiKeyRequest( name: 'Updated Integration Key', description: 'Updated description' ) ); ``` ### `revokePartnerApiKey()` Revoke a partner API key. ```php $result = TurboPartner::revokePartnerApiKey('partner-key-uuid-here'); ``` --- ## Partner User Management ### `addUserToPartnerPortal()` Add a user to the partner portal with specific permissions. ```php use TurboDocx\Types\Requests\Partner\AddPartnerUserRequest; use TurboDocx\Types\Partner\PartnerPermissions; $result = TurboPartner::addUserToPartnerPortal( new AddPartnerUserRequest( email: 'admin@partner.com', role: 'admin', // admin, member, or viewer permissions: new PartnerPermissions( canManageOrgs: true, canManageOrgUsers: true, canManagePartnerUsers: false, canManageOrgAPIKeys: true, canManagePartnerAPIKeys: false, canUpdateEntitlements: true, canViewAuditLogs: true ) ) ); echo "Partner User ID: {$result->data->id}\n"; ``` ### `listPartnerPortalUsers()` List all partner portal users. ```php use TurboDocx\Types\Requests\Partner\ListPartnerUsersRequest; $result = TurboPartner::listPartnerPortalUsers( new ListPartnerUsersRequest(limit: 50) ); foreach ($result->results as $user) { echo "- {$user->email} (Role: {$user->role})\n"; } ``` ### `updatePartnerUserPermissions()` Update a partner user's role and permissions. ```php use TurboDocx\Types\Requests\Partner\UpdatePartnerUserRequest; use TurboDocx\Types\Partner\PartnerPermissions; $result = TurboPartner::updatePartnerUserPermissions( 'partner-user-uuid-here', new UpdatePartnerUserRequest( role: 'admin', permissions: new PartnerPermissions( canManageOrgs: true, canManageOrgUsers: true, canManagePartnerUsers: true, canManageOrgAPIKeys: true, canManagePartnerAPIKeys: true, canUpdateEntitlements: true, canViewAuditLogs: true ) ) ); ``` ### `resendPartnerPortalInvitationToUser()` Resend the invitation email to a pending partner user. ```php $result = TurboPartner::resendPartnerPortalInvitationToUser('partner-user-uuid-here'); ``` ### `removeUserFromPartnerPortal()` Remove a user from the partner portal. ```php $result = TurboPartner::removeUserFromPartnerPortal('partner-user-uuid-here'); ``` --- ## Audit Logs ### `getPartnerAuditLogs()` Get audit logs for all partner activities with filtering. ```php use TurboDocx\Types\Requests\Partner\ListAuditLogsRequest; $result = TurboPartner::getPartnerAuditLogs( new ListAuditLogsRequest( limit: 50, offset: 0, action: 'ORG_CREATED', // Optional filter by action resourceType: 'organization', // Optional filter by resource type success: true, // Optional filter by success/failure startDate: '2024-01-01', // Optional date range start endDate: '2024-12-31' // Optional date range end ) ); foreach ($result->results as $entry) { echo "{$entry->createdOn} - {$entry->action}"; if ($entry->resourceType) { echo " ({$entry->resourceType})"; } echo " - " . ($entry->success ? 'Success' : 'Failed') . "\n"; } ``` --- ## Entitlements Reference ### Features (Settable Limits) These are limits and capabilities you can configure for each organization: | Field | Type | Description | |-------|------|-------------| | `maxUsers` | int | Maximum users allowed (-1 = unlimited) | | `maxProjectspaces` | int | Maximum projectspaces | | `maxTemplates` | int | Maximum templates | | `maxStorage` | int | Maximum storage in bytes | | `maxGeneratedDeliverables` | int | Maximum generated documents | | `maxSignatures` | int | Maximum e-signatures | | `maxAICredits` | int | Maximum AI credits | | `rdWatermark` | bool | Enable RapidDocx watermark | | `hasFileDownload` | bool | Enable file download | | `hasAdvancedDateFormats` | bool | Enable advanced date formats | | `hasGDrive` | bool | Enable Google Drive integration | | `hasSharepoint` | bool | Enable SharePoint integration | | `hasSharepointOnly` | bool | SharePoint-only mode | | `hasTDAI` | bool | Enable TurboDocx AI features | | `hasPptx` | bool | Enable PowerPoint support | | `hasTDWriter` | bool | Enable TurboDocx Writer | | `hasSalesforce` | bool | Enable Salesforce integration | | `hasWrike` | bool | Enable Wrike integration | | `hasVariableStack` | bool | Enable variable stack | | `hasSubvariables` | bool | Enable subvariables | | `hasZapier` | bool | Enable Zapier integration | | `hasBYOM` | bool | Enable Bring Your Own Model | | `hasBYOVS` | bool | Enable Bring Your Own Vector Store | | `hasBetaFeatures` | bool | Enable beta features | | `enableBulkSending` | bool | Enable bulk document sending | ### Tracking (Read-Only Usage) These are usage counters that are read-only: | Field | Type | Description | |-------|------|-------------| | `numUsers` | int | Current number of users | | `numProjectspaces` | int | Current number of projectspaces | | `numTemplates` | int | Current number of templates | | `storageUsed` | int | Current storage used in bytes | | `numGeneratedDeliverables` | int | Total documents generated | | `numSignaturesUsed` | int | Total signatures used | | `currentAICredits` | int | Remaining AI credits | --- ## Enums and Types ### PartnerScope (22 Scopes) ```php use TurboDocx\Types\Enums\PartnerScope; // Organization CRUD PartnerScope::ORG_CREATE // 'org:create' PartnerScope::ORG_READ // 'org:read' PartnerScope::ORG_UPDATE // 'org:update' PartnerScope::ORG_DELETE // 'org:delete' // Entitlements PartnerScope::ENTITLEMENTS_UPDATE // 'entitlements:update' // Organization Users PartnerScope::ORG_USERS_CREATE // 'org-users:create' PartnerScope::ORG_USERS_READ // 'org-users:read' PartnerScope::ORG_USERS_UPDATE // 'org-users:update' PartnerScope::ORG_USERS_DELETE // 'org-users:delete' // Partner Users PartnerScope::PARTNER_USERS_CREATE // 'partner-users:create' PartnerScope::PARTNER_USERS_READ // 'partner-users:read' PartnerScope::PARTNER_USERS_UPDATE // 'partner-users:update' PartnerScope::PARTNER_USERS_DELETE // 'partner-users:delete' // Organization API Keys PartnerScope::ORG_APIKEYS_CREATE // 'org-apikeys:create' PartnerScope::ORG_APIKEYS_READ // 'org-apikeys:read' PartnerScope::ORG_APIKEYS_UPDATE // 'org-apikeys:update' PartnerScope::ORG_APIKEYS_DELETE // 'org-apikeys:delete' // Partner API Keys PartnerScope::PARTNER_APIKEYS_CREATE // 'partner-apikeys:create' PartnerScope::PARTNER_APIKEYS_READ // 'partner-apikeys:read' PartnerScope::PARTNER_APIKEYS_UPDATE // 'partner-apikeys:update' PartnerScope::PARTNER_APIKEYS_DELETE // 'partner-apikeys:delete' // Audit PartnerScope::AUDIT_READ // 'audit:read' ``` ### OrgUserRole (Organization Users) ```php use TurboDocx\Types\Enums\OrgUserRole; OrgUserRole::ADMIN // Full organization access OrgUserRole::CONTRIBUTOR // Can create and edit content OrgUserRole::USER // Standard user access OrgUserRole::VIEWER // Read-only access ``` ### PartnerUserRole (Partner Portal Users) ```php use TurboDocx\Types\Enums\PartnerUserRole; PartnerUserRole::ADMIN // Full partner portal access PartnerUserRole::MEMBER // Standard partner access (respects permissions) PartnerUserRole::VIEWER // Read-only access to partner portal ``` ### PartnerPermissions ```php use TurboDocx\Types\Partner\PartnerPermissions; $permissions = new PartnerPermissions( canManageOrgs: true, // Create, update, delete organizations canManageOrgUsers: true, // Manage users within organizations canManagePartnerUsers: false, // Manage other partner portal users canManageOrgAPIKeys: true, // Manage organization API keys canManagePartnerAPIKeys: false, // Manage partner API keys canUpdateEntitlements: true, // Update organization entitlements canViewAuditLogs: true // View audit logs ); ``` --- ## Error Handling The SDK provides typed exceptions for different error scenarios: ```php use TurboDocx\Exceptions\AuthenticationException; use TurboDocx\Exceptions\ValidationException; use TurboDocx\Exceptions\NotFoundException; use TurboDocx\Exceptions\RateLimitException; use TurboDocx\Exceptions\NetworkException; try { $result = TurboPartner::createOrganization(/* ... */); } catch (AuthenticationException $e) { // 401 - Invalid API key or partner ID echo "Authentication failed: {$e->getMessage()}\n"; } catch (ValidationException $e) { // 400 - Invalid request data echo "Validation error: {$e->getMessage()}\n"; } catch (NotFoundException $e) { // 404 - Organization or resource not found echo "Not found: {$e->getMessage()}\n"; } catch (RateLimitException $e) { // 429 - Rate limit exceeded echo "Rate limit: {$e->getMessage()}\n"; } catch (NetworkException $e) { // Network/connection error echo "Network error: {$e->getMessage()}\n"; } ``` ### Error Classes | Error Class | Status Code | Description | |-------------|-------------|-------------| | `TurboDocxException` | varies | Base exception for all SDK errors | | `AuthenticationException` | 401 | Invalid or missing API credentials | | `ValidationException` | 400 | Invalid request parameters | | `NotFoundException` | 404 | Resource not found | | `RateLimitException` | 429 | Too many requests | | `NetworkException` | - | Network connectivity issues | --- ## Complete Example Here's a complete example showing a typical partner workflow: ```php data->id}\n"; // 2. Set up their entitlements based on their plan TurboPartner::updateOrganizationEntitlements( $org->data->id, new UpdateEntitlementsRequest( features: [ 'maxUsers' => 25, 'maxStorage' => 5368709120, // 5GB 'hasTDAI' => true, 'hasFileDownload' => true, ] ) ); echo "Configured entitlements\n"; // 3. Add their admin user $user = TurboPartner::addUserToOrganization( $org->data->id, new AddOrgUserRequest( email: 'admin@newcustomer.com', role: OrgUserRole::ADMIN ) ); echo "Added admin user: {$user->data->email}\n"; // 4. Create an API key for their integration $apiKey = TurboPartner::createOrganizationApiKey( $org->data->id, new CreateOrgApiKeyRequest( name: 'Production API Key', role: 'admin' ) ); echo "Created API key: {$apiKey->data->key}\n"; echo "\nCustomer setup complete!\n"; } catch (TurboDocxException $e) { echo "Error: {$e->getMessage()}\n"; exit(1); } ``` --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) - [Packagist Package](https://packagist.org/packages/turbodocx/sdk) - [TurboSign PHP SDK](/docs/SDKs/php) — For digital signature operations - [API Reference](/docs/API/partner-api) — REST API documentation --- # TurboPartner Python SDK :::tip Interested in TurboPartner? TurboPartner is available for integrators and partners. [Contact us](https://www.turbodocx.com/demo) to get started. ::: The official TurboDocx Partner SDK for Python applications. Build multi-tenant SaaS applications with programmatic organization management, user provisioning, API key management, and entitlement control. Fully async with `httpx`. :::info What is TurboPartner? TurboPartner is the partner management API for TurboDocx. It allows you to programmatically create and manage organizations, users, API keys, and feature entitlements — perfect for building white-label or multi-tenant applications on top of TurboDocx. ::: ## TLDR ```python from turbodocx_sdk import TurboPartner async def main(): # 1. Configure TurboPartner.configure( partner_api_key=os.environ["TURBODOCX_PARTNER_API_KEY"], partner_id=os.environ["TURBODOCX_PARTNER_ID"], ) # 2. Create an organization with entitlements org = await TurboPartner.create_organization( "Acme Corporation", features={ "maxUsers": 25, # Max users allowed "maxStorage": 5 * 1024 * 1024 * 1024, # 5GB in bytes "hasTDAI": True, # Enable TurboDocx AI }, ) org_id = org["data"]["id"] # 3. Add a user user = await TurboPartner.add_user_to_organization( org_id, email="admin@acme.com", role="admin" ) print(f"User: {user['data']['email']}") # 4. Create an API key key = await TurboPartner.create_organization_api_key( org_id, name="Production Key", role="admin" ) print(f"API Key: {key['data']['key']}") # Save this — only shown once! asyncio.run(main()) ``` --- ## Installation ```bash pip install turbodocx-sdk # or poetry add turbodocx-sdk ``` ## Requirements - Python 3.9 or higher - `httpx` (installed automatically) --- ## Configuration ```python from turbodocx_sdk import TurboPartner # Configure with your partner credentials TurboPartner.configure( partner_api_key=os.environ["TURBODOCX_PARTNER_API_KEY"], # Required: Must start with TDXP- partner_id=os.environ["TURBODOCX_PARTNER_ID"], # Required: Your partner UUID ) ``` ```python from turbodocx_sdk import TurboPartner # Auto-configure from environment variables # Reads from TURBODOCX_PARTNER_API_KEY and TURBODOCX_PARTNER_ID TurboPartner.configure() ``` :::caution Partner API Key Required Partner API keys start with `TDXP-` prefix. These are different from regular organization API keys and provide access to partner-level operations across all your organizations. ::: ### Environment Variables ```bash # .env or shell environment export TURBODOCX_PARTNER_API_KEY=TDXP-your-partner-api-key export TURBODOCX_PARTNER_ID=your-partner-uuid ``` --- ## Quick Start ### Create Your First Organization ```python from turbodocx_sdk import TurboPartner TurboPartner.configure( partner_api_key=os.environ["TURBODOCX_PARTNER_API_KEY"], partner_id=os.environ["TURBODOCX_PARTNER_ID"], ) # Create a new organization result = await TurboPartner.create_organization("Acme Corporation") print(f"Organization created!") print(f" ID: {result['data']['id']}") print(f" Name: {result['data']['name']}") ``` :::caution Always Handle Errors The above examples omit error handling for brevity. In production, always wrap calls in try/except. See [Error Handling](#error-handling) for complete patterns. ::: --- ## Organization Management ### `create_organization()` Create a new organization under your partner account. ```python result = await TurboPartner.create_organization( "Acme Corporation", features={"maxUsers": 50}, # Optional entitlements override metadata={"plan": "enterprise"}, # Optional metadata key-value pairs ) print(f"Organization ID: {result['data']['id']}") ``` ### `list_organizations()` List all organizations with pagination and search. ```python result = await TurboPartner.list_organizations( limit=25, offset=0, search="Acme", # Optional search by name ) print(f"Total: {result['data']['totalRecords']}") for org in result["data"]["results"]: print(f"- {org['name']} (ID: {org['id']})") ``` ### `get_organization_details()` Get full details including features and tracking for an organization. ```python result = await TurboPartner.get_organization_details("org-uuid-here") print(f"Name: {result['data']['name']}") print(f"Active: {result['data']['isActive']}") features = result["data"].get("features", {}) if features.get("maxUsers") is not None: print(f"Max Users: {features['maxUsers']}") if features.get("maxStorage") is not None: print(f"Max Storage: {features['maxStorage']} bytes") tracking = result["data"].get("tracking", {}) print(f"Current Users: {tracking.get('numUsers', 0)}") print(f"Storage Used: {tracking.get('storageUsed', 0)} bytes") ``` ### `update_organization_info()` Update an organization's name. ```python result = await TurboPartner.update_organization_info( "org-uuid-here", name="Acme Corp (Updated)", ) ``` ### `update_organization_entitlements()` Update an organization's feature limits and capabilities. ```python result = await TurboPartner.update_organization_entitlements( "org-uuid-here", features={ "maxUsers": 100, "maxStorage": 10 * 1024 * 1024 * 1024, # 10GB "maxSignatures": 500, "hasTDAI": True, "hasFileDownload": True, "hasBetaFeatures": False, }, tracking={"numUsers": 5}, # Optional: set tracking counters ) print("Entitlements updated!") ``` :::info Features vs Tracking **Features** are limits and capabilities you can set (maxUsers, hasTDAI, etc.). **Tracking** is read-only usage data (numUsers, storageUsed, etc.). See [Entitlements Reference](#entitlements-reference) for all available fields. ::: ### `delete_organization()` Delete an organization (soft delete). ```python result = await TurboPartner.delete_organization("org-uuid-here") print(f"Success: {result['success']}") ``` :::danger Use With Caution Deleting an organization is a destructive operation. All organization data, users, and API keys will be affected. ::: --- ## Organization User Management ### `add_user_to_organization()` Add a user to an organization with a specific role. ```python result = await TurboPartner.add_user_to_organization( "org-uuid-here", email="user@example.com", role="admin", # admin, contributor, user, or viewer ) print(f"User ID: {result['data']['id']}") print(f"Invitation sent to: {result['data']['email']}") ``` ### `list_organization_users()` List all users in an organization. ```python result = await TurboPartner.list_organization_users( "org-uuid-here", limit=50, offset=0, search="admin", # Optional search by email or name ) print(f"Total Users: {result['data']['totalRecords']}") for user in result["data"]["results"]: print(f"- {user['email']} ({user['role']})") ``` ### `update_organization_user_role()` Change a user's role within an organization. ```python result = await TurboPartner.update_organization_user_role( "org-uuid-here", "user-uuid-here", role="contributor", ) ``` ### `resend_organization_invitation_to_user()` Resend the invitation email to a pending user. ```python result = await TurboPartner.resend_organization_invitation_to_user( "org-uuid-here", "user-uuid-here", ) ``` ### `remove_user_from_organization()` Remove a user from an organization. ```python result = await TurboPartner.remove_user_from_organization( "org-uuid-here", "user-uuid-here", ) ``` --- ## Organization API Key Management ### `create_organization_api_key()` Create an API key for an organization. ```python result = await TurboPartner.create_organization_api_key( "org-uuid-here", name="Production API Key", role="admin", # admin, contributor, or viewer ) print(f"Key ID: {result['data']['id']}") print(f"Full Key: {result['data']['key']}") # Only shown once! ``` :::caution Save Your API Key The full API key is only returned once during creation. Store it securely — you won't be able to retrieve it again. ::: ### `list_organization_api_keys()` List all API keys for an organization. ```python result = await TurboPartner.list_organization_api_keys( "org-uuid-here", limit=50, offset=0, # Optional pagination offset search="prod", # Optional search by name ) for key in result["data"]["results"]: print(f"- {key['name']} (Role: {key['role']})") ``` ### `update_organization_api_key()` Update an organization API key's name or role. ```python result = await TurboPartner.update_organization_api_key( "org-uuid-here", "api-key-uuid-here", name="Updated Key Name", role="contributor", ) ``` ### `revoke_organization_api_key()` Revoke (delete) an organization API key. ```python result = await TurboPartner.revoke_organization_api_key( "org-uuid-here", "api-key-uuid-here", ) ``` --- ## Partner API Key Management ### `create_partner_api_key()` Create a new partner-level API key with specific scopes. ```python from turbodocx_sdk import ( SCOPE_ORG_CREATE, SCOPE_ORG_READ, SCOPE_ORG_UPDATE, SCOPE_ENTITLEMENTS_UPDATE, SCOPE_AUDIT_READ, ) result = await TurboPartner.create_partner_api_key( name="Integration API Key", scopes=[ SCOPE_ORG_CREATE, SCOPE_ORG_READ, SCOPE_ORG_UPDATE, SCOPE_ENTITLEMENTS_UPDATE, SCOPE_AUDIT_READ, ], description="For third-party integration", ) print(f"Key ID: {result['data']['id']}") print(f"Full Key: {result['data']['key']}") # Only shown once! ``` ### `list_partner_api_keys()` List all partner API keys. ```python result = await TurboPartner.list_partner_api_keys( limit=50, offset=0, # Optional pagination offset search="integ", # Optional search by name ) for key in result["data"]["results"]: print(f"- {key['name']}") print(f" Scopes: {key['scopes']}") ``` ### `update_partner_api_key()` Update a partner API key. ```python result = await TurboPartner.update_partner_api_key( "partner-key-uuid-here", name="Updated Integration Key", description="Updated description", scopes=[SCOPE_ORG_READ, SCOPE_AUDIT_READ], # Optional: update scopes ) ``` ### `revoke_partner_api_key()` Revoke a partner API key. ```python result = await TurboPartner.revoke_partner_api_key("partner-key-uuid-here") ``` --- ## Partner User Management ### `add_user_to_partner_portal()` Add a user to the partner portal with specific permissions. ```python result = await TurboPartner.add_user_to_partner_portal( email="admin@partner.com", role="admin", # admin, member, or viewer permissions={ "canManageOrgs": True, "canManageOrgUsers": True, "canManagePartnerUsers": False, "canManageOrgAPIKeys": True, "canManagePartnerAPIKeys": False, "canUpdateEntitlements": True, "canViewAuditLogs": True, }, ) print(f"Partner User ID: {result['data']['id']}") ``` ### `list_partner_portal_users()` List all partner portal users. ```python result = await TurboPartner.list_partner_portal_users( limit=50, offset=0, # Optional pagination offset search="admin", # Optional search by email or name ) for user in result["data"]["results"]: print(f"- {user['email']} (Role: {user['role']})") ``` ### `update_partner_user_permissions()` Update a partner user's role and permissions. ```python result = await TurboPartner.update_partner_user_permissions( "partner-user-uuid-here", role="admin", permissions={ "canManageOrgs": True, "canManageOrgUsers": True, "canManagePartnerUsers": True, "canManageOrgAPIKeys": True, "canManagePartnerAPIKeys": True, "canUpdateEntitlements": True, "canViewAuditLogs": True, }, ) ``` ### `resend_partner_portal_invitation_to_user()` Resend the invitation email to a pending partner user. ```python result = await TurboPartner.resend_partner_portal_invitation_to_user( "partner-user-uuid-here" ) ``` ### `remove_user_from_partner_portal()` Remove a user from the partner portal. ```python result = await TurboPartner.remove_user_from_partner_portal( "partner-user-uuid-here" ) ``` --- ## Audit Logs ### `get_partner_audit_logs()` Get audit logs for all partner activities with filtering. ```python result = await TurboPartner.get_partner_audit_logs( limit=50, offset=0, search="acme", # Optional search query action="ORG_CREATED", # Optional filter by action resource_type="organization", # Optional filter by resource type resource_id="org-uuid-here", # Optional filter by resource ID success=True, # Optional filter by success/failure start_date="2024-01-01", # Optional date range start end_date="2024-12-31", # Optional date range end ) for entry in result["data"]["results"]: status = "Success" if entry["success"] else "Failed" resource = f" ({entry['resourceType']})" if entry.get("resourceType") else "" print(f"{entry['createdOn']} - {entry['action']}{resource} - {status}") ``` --- ## Entitlements Reference ### Features (Settable Limits) These are limits and capabilities you can configure for each organization: | Field | Type | Description | |-------|------|-------------| | `maxUsers` | `int` | Maximum users allowed (-1 = unlimited) | | `maxProjectspaces` | `int` | Maximum projectspaces | | `maxTemplates` | `int` | Maximum templates | | `maxStorage` | `int` | Maximum storage in bytes | | `maxGeneratedDeliverables` | `int` | Maximum generated documents | | `maxSignatures` | `int` | Maximum e-signatures | | `maxAICredits` | `int` | Maximum AI credits | | `rdWatermark` | `bool` | Enable RapidDocx watermark | | `hasFileDownload` | `bool` | Enable file download | | `hasAdvancedDateFormats` | `bool` | Enable advanced date formats | | `hasGDrive` | `bool` | Enable Google Drive integration | | `hasSharepoint` | `bool` | Enable SharePoint integration | | `hasSharepointOnly` | `bool` | SharePoint-only mode | | `hasTDAI` | `bool` | Enable TurboDocx AI features | | `hasPptx` | `bool` | Enable PowerPoint support | | `hasTDWriter` | `bool` | Enable TurboDocx Writer | | `hasSalesforce` | `bool` | Enable Salesforce integration | | `hasWrike` | `bool` | Enable Wrike integration | | `hasVariableStack` | `bool` | Enable variable stack | | `hasSubvariables` | `bool` | Enable subvariables | | `hasZapier` | `bool` | Enable Zapier integration | | `hasBYOM` | `bool` | Enable Bring Your Own Model | | `hasBYOVS` | `bool` | Enable Bring Your Own Vector Store | | `hasBetaFeatures` | `bool` | Enable beta features | | `enableBulkSending` | `bool` | Enable bulk document sending | :::tip Dictionary-Based Configuration Pass features as a plain dictionary with camelCase keys. The SDK sends them directly to the API: ```python features={"maxUsers": 25, "hasTDAI": True} ``` ::: ### Tracking (Read-Only Usage) These are usage counters that are read-only: | Field | Type | Description | |-------|------|-------------| | `numUsers` | `int` | Current number of users | | `numProjectspaces` | `int` | Current number of projectspaces | | `numTemplates` | `int` | Current number of templates | | `storageUsed` | `int` | Current storage used in bytes | | `numGeneratedDeliverables` | `int` | Total documents generated | | `numSignaturesUsed` | `int` | Total signatures used | | `currentAICredits` | `int` | Remaining AI credits | --- ## Scope Constants ### Partner Scopes (22 Scopes) ```python from turbodocx_sdk import ( # Organization CRUD SCOPE_ORG_CREATE, # "org:create" SCOPE_ORG_READ, # "org:read" SCOPE_ORG_UPDATE, # "org:update" SCOPE_ORG_DELETE, # "org:delete" # Entitlements SCOPE_ENTITLEMENTS_UPDATE, # "entitlements:update" # Organization Users SCOPE_ORG_USERS_CREATE, # "org-users:create" SCOPE_ORG_USERS_READ, # "org-users:read" SCOPE_ORG_USERS_UPDATE, # "org-users:update" SCOPE_ORG_USERS_DELETE, # "org-users:delete" # Partner Users SCOPE_PARTNER_USERS_CREATE, # "partner-users:create" SCOPE_PARTNER_USERS_READ, # "partner-users:read" SCOPE_PARTNER_USERS_UPDATE, # "partner-users:update" SCOPE_PARTNER_USERS_DELETE, # "partner-users:delete" # Organization API Keys SCOPE_ORG_APIKEYS_CREATE, # "org-apikeys:create" SCOPE_ORG_APIKEYS_READ, # "org-apikeys:read" SCOPE_ORG_APIKEYS_UPDATE, # "org-apikeys:update" SCOPE_ORG_APIKEYS_DELETE, # "org-apikeys:delete" # Partner API Keys SCOPE_PARTNER_APIKEYS_CREATE, # "partner-apikeys:create" SCOPE_PARTNER_APIKEYS_READ, # "partner-apikeys:read" SCOPE_PARTNER_APIKEYS_UPDATE, # "partner-apikeys:update" SCOPE_PARTNER_APIKEYS_DELETE, # "partner-apikeys:delete" # Audit SCOPE_AUDIT_READ, # "audit:read" ) ``` ### Organization User Roles | Role | Description | |------|-------------| | `"admin"` | Full organization access | | `"contributor"` | Can create and edit content | | `"user"` | Standard user access | | `"viewer"` | Read-only access | ### Partner User Roles | Role | Description | |------|-------------| | `"admin"` | Full partner portal access | | `"member"` | Standard partner access (respects permissions) | | `"viewer"` | Read-only access to partner portal | ### Partner Permissions ```python permissions = { "canManageOrgs": True, # Create, update, delete organizations "canManageOrgUsers": True, # Manage users within organizations "canManagePartnerUsers": False, # Manage other partner portal users "canManageOrgAPIKeys": True, # Manage organization API keys "canManagePartnerAPIKeys": False, # Manage partner API keys "canUpdateEntitlements": True, # Update organization entitlements "canViewAuditLogs": True, # View audit logs } ``` --- ## Error Handling The SDK provides typed exceptions for different error scenarios: ```python from turbodocx_sdk import ( TurboPartner, TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, ) try: result = await TurboPartner.create_organization("Acme Corp") except AuthenticationError as e: # 401 - Invalid API key or partner ID print(f"Authentication failed: {e}") print(f" HTTP Status: {e.status_code}") except ValidationError as e: # 400 - Invalid request data print(f"Validation error: {e}") print(f" HTTP Status: {e.status_code}") except NotFoundError as e: # 404 - Resource not found print(f"Not found: {e}") except RateLimitError as e: # 429 - Too many requests print(f"Rate limited: {e}") except NetworkError as e: # Network connectivity issues print(f"Network error: {e}") except TurboDocxError as e: # Any other API error print(f"API error: {e}") if hasattr(e, "status_code"): print(f" HTTP Status: {e.status_code}") if hasattr(e, "code") and e.code: print(f" Error Code: {e.code}") ``` ### Error Types | Error Type | Status Code | Description | |------------|-------------|-------------| | `TurboDocxError` | varies | Base error for all SDK errors | | `AuthenticationError` | 401 | Invalid or missing API credentials | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Resource not found | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | --- ## Complete Example Here's a complete example showing a typical partner workflow: ```python from turbodocx_sdk import TurboPartner, TurboDocxError async def main(): # Configure TurboPartner.configure( partner_api_key=os.environ["TURBODOCX_PARTNER_API_KEY"], partner_id=os.environ["TURBODOCX_PARTNER_ID"], ) # 1. Create an organization for a new customer org = await TurboPartner.create_organization("New Customer Inc") org_id = org["data"]["id"] print(f"Created organization: {org_id}") # 2. Set up their entitlements based on their plan await TurboPartner.update_organization_entitlements( org_id, features={ "maxUsers": 25, "maxStorage": 5 * 1024 * 1024 * 1024, # 5GB "hasTDAI": True, "hasFileDownload": True, }, ) print("Configured entitlements") # 3. Add their admin user user = await TurboPartner.add_user_to_organization( org_id, email="admin@newcustomer.com", role="admin" ) print(f"Added admin user: {user['data']['email']}") # 4. Create an API key for their integration api_key = await TurboPartner.create_organization_api_key( org_id, name="Production API Key", role="admin" ) print(f"Created API key: {api_key['data']['key']}") print("\nCustomer setup complete!") asyncio.run(main()) ``` --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) - [PyPI Package](https://pypi.org/project/turbodocx-sdk/) - [TurboSign Python SDK](/docs/SDKs/python) — For digital signature operations - [SDKs Overview](/docs/SDKs/) — All TurboDocx SDKs --- # PHP SDK The official TurboDocx SDK for PHP applications. Build document generation and digital signature workflows with modern PHP 8.1+ features, strong typing, and comprehensive error handling. Available on Packagist as `turbodocx/sdk`. ## Installation ```bash composer require turbodocx/sdk ``` ## Requirements - PHP 8.1 or higher - Composer - ext-json - ext-fileinfo :::tip Modern PHP Features This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience. ::: --- ## Configuration ```php ```php :::warning Sender Email Required The `senderEmail` parameter is **required** for TurboSign. This email appears as the reply-to address in signature request emails. If you omit it, `configure()` throws a `ValidationException` before any request is sent (unless `skipSenderValidation` is enabled, which TurboSign does not use). ::: ### Environment Variables ```bash # .env TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here TURBODOCX_SENDER_EMAIL=you@company.com TURBODOCX_SENDER_NAME=Your Company Name ``` :::warning API Credentials Required Both `apiKey` and `orgId` parameters are **required** for all API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Send a Document for Signature ```php documentId}\n"; ``` :::warning Always Handle Errors The above examples omit error handling for brevity. In production, wrap all TurboSign calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: ### Using Template-Based Fields ```php 200, 'height' => 50] ) ), new Field( type: SignatureFieldType::DATE, recipientEmail: 'alice@example.com', template: new TemplateConfig( anchor: '{DATE_ALICE}', placement: FieldPlacement::REPLACE, size: ['width' => 100, 'height' => 30] ) ) ], fileLink: 'https://www.turbodocx.com/examples/turbodocx.pdf', senderName: 'Your Company', senderEmail: 'sender@company.com' ) ); ``` :::warning Always Handle Errors The above examples omit error handling for brevity. In production, wrap all TurboSign calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: :::info Template Anchors Required **Important:** The document file must contain the anchor text (e.g., `{SIGNATURE_ALICE}`, `{DATE_ALICE}`) that you reference in your fields. If the anchors don't exist in the document, the API will return an error. ::: --- ## File Input Methods TurboSign supports four different ways to provide document files: ### 1. File Upload (Direct) ```php 200, 'height' => 50] ) ) ] ) ); ``` :::info Integration with TurboDocx `templateId` references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents. ::: --- ## API Reference ### Configure Configure the SDK with your API credentials and organization settings. ```php use TurboDocx\TurboSign; use TurboDocx\Config\HttpClientConfig; // Manual configuration TurboSign::configure(new HttpClientConfig( apiKey: 'your-api-key', orgId: 'your-org-id', senderEmail: 'you@company.com', senderName: 'Your Company' )); // Or from environment TurboSign::configure(HttpClientConfig::fromEnvironment()); ``` ### Prepare for review Upload a document for preview without sending signature request emails. ```php use TurboDocx\Types\Recipient; use TurboDocx\Types\Field; use TurboDocx\Types\SignatureFieldType; use TurboDocx\Types\Requests\CreateSignatureReviewLinkRequest; $result = TurboSign::createSignatureReviewLink( new CreateSignatureReviewLinkRequest( recipients: [ new Recipient('John Doe', 'john@example.com', 1) ], fields: [ new Field( type: SignatureFieldType::SIGNATURE, recipientEmail: 'john@example.com', page: 1, x: 100, y: 500, width: 200, height: 50 ) ], fileLink: 'https://www.turbodocx.com/examples/turbodocx.pdf', documentName: 'Contract Draft' ) ); echo "Preview URL: {$result->previewUrl}\n"; echo "Document ID: {$result->documentId}\n"; ``` ### Prepare for signing Upload a document and immediately send signature requests to all recipients. ```php use TurboDocx\Types\Recipient; use TurboDocx\Types\Field; use TurboDocx\Types\SignatureFieldType; use TurboDocx\Types\Requests\SendSignatureRequest; $result = TurboSign::sendSignature( new SendSignatureRequest( recipients: [ new Recipient('Recipient Name', 'recipient@example.com', 1) ], fields: [ new Field( type: SignatureFieldType::SIGNATURE, recipientEmail: 'recipient@example.com', page: 1, x: 100, y: 500, width: 200, height: 50 ) ], fileLink: 'https://www.turbodocx.com/examples/turbodocx.pdf', documentName: 'Service Agreement' ) ); echo "Document ID: {$result->documentId}\n"; ``` ### Get status Retrieve the current status of a document. ```php $status = TurboSign::getStatus('document-uuid'); echo "Document Status: {$status->status}\n"; // 'pending', 'completed', 'voided' ``` ### Download document Download the completed signed document as PDF content. ```php $pdfContent = TurboSign::download('document-uuid'); // Save to file file_put_contents('signed-contract.pdf', $pdfContent); // Or send as HTTP response header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="signed.pdf"'); echo $pdfContent; ``` ### Void Cancel/void a signature request that hasn't been completed. ```php use TurboDocx\Types\Responses\VoidDocumentResponse; $result = TurboSign::void('document-uuid', 'Document needs to be revised'); echo "Document ID: {$result->id}\n"; echo "Status: {$result->status}\n"; echo "Void Reason: {$result->voidReason}\n"; ``` ### Resend Resend signature request emails to specific recipients. ```php // Resend to specific recipients $result = TurboSign::resend('document-uuid', ['recipient-id-1', 'recipient-id-2']); // Resend to all recipients $result = TurboSign::resend('document-uuid', []); echo "Recipients notified: {$result->recipientCount}\n"; ``` ### Get audit trail Retrieve the complete audit trail for a document, including all events and actions. ```php $audit = TurboSign::getAuditTrail('document-uuid'); echo "Audit Trail:\n"; foreach ($audit->auditTrail as $entry) { echo " {$entry->timestamp} - {$entry->actionType}"; if ($entry->user) { echo " by {$entry->user->name}"; } echo "\n"; } ``` --- ## Field Types TurboSign supports 11 different field types using PHP enums: ```php use TurboDocx\Types\SignatureFieldType; SignatureFieldType::SIGNATURE // Signature field SignatureFieldType::INITIAL // Initial field SignatureFieldType::DATE // Date stamp (auto-filled when signed) SignatureFieldType::TEXT // Free text input SignatureFieldType::FULL_NAME // Full name (auto-filled from recipient) SignatureFieldType::FIRST_NAME // First name SignatureFieldType::LAST_NAME // Last name SignatureFieldType::EMAIL // Email address SignatureFieldType::TITLE // Job title SignatureFieldType::COMPANY // Company name SignatureFieldType::CHECKBOX // Checkbox field ``` ### Field Positioning TurboSign supports two ways to position fields: #### 1. Coordinate-Based (Pixel Perfect) ```php new Field( type: SignatureFieldType::SIGNATURE, recipientEmail: 'john@example.com', page: 1, // Page number (1-indexed) x: 100, // X coordinate y: 500, // Y coordinate width: 200, // Width in pixels height: 50 // Height in pixels ) ``` #### 2. Template Anchors (Dynamic) ```php use TurboDocx\Types\TemplateConfig; use TurboDocx\Types\FieldPlacement; new Field( type: SignatureFieldType::SIGNATURE, recipientEmail: 'john@example.com', template: new TemplateConfig( anchor: '{signature1}', // Text to find in PDF placement: FieldPlacement::REPLACE, // How to place the field size: ['width' => 100, 'height' => 30] ) ) ``` **Placement Options:** - `FieldPlacement::REPLACE` - Replace the anchor text - `FieldPlacement::BEFORE` - Place before the anchor - `FieldPlacement::AFTER` - Place after the anchor - `FieldPlacement::ABOVE` - Place above the anchor - `FieldPlacement::BELOW` - Place below the anchor ### Advanced Field Options ```php // Checkbox (pre-checked, readonly) new Field( type: SignatureFieldType::CHECKBOX, recipientEmail: 'john@example.com', page: 1, x: 100, y: 600, width: 20, height: 20, defaultValue: 'true', // Pre-checked isReadonly: true // Cannot be unchecked ) // Multiline text field new Field( type: SignatureFieldType::TEXT, recipientEmail: 'john@example.com', page: 1, x: 100, y: 200, width: 400, height: 100, isMultiline: true, // Allow multiple lines required: true, // Field is required backgroundColor: '#f0f0f0' // Background color ) // Readonly text (pre-filled, non-editable) new Field( type: SignatureFieldType::TEXT, recipientEmail: 'john@example.com', page: 1, x: 100, y: 300, width: 300, height: 30, defaultValue: 'This text is pre-filled', isReadonly: true ) ``` --- ## Error Handling The SDK provides typed exceptions for different error scenarios: ```php use TurboDocx\Exceptions\AuthenticationException; use TurboDocx\Exceptions\ValidationException; use TurboDocx\Exceptions\NotFoundException; use TurboDocx\Exceptions\RateLimitException; use TurboDocx\Exceptions\NetworkException; try { $result = TurboSign::sendSignature(/* ... */); } catch (AuthenticationException $e) { // 401 - Invalid API key or access token echo "Authentication failed: {$e->getMessage()}\n"; } catch (ValidationException $e) { // 400 - Invalid request data echo "Validation error: {$e->getMessage()}\n"; } catch (NotFoundException $e) { // 404 - Document not found echo "Not found: {$e->getMessage()}\n"; } catch (RateLimitException $e) { // 429 - Rate limit exceeded echo "Rate limit: {$e->getMessage()}\n"; } catch (NetworkException $e) { // Network/connection error echo "Network error: {$e->getMessage()}\n"; } ``` ### Error Classes | Error Class | Status Code | Description | | ------------------------- | ----------- | ---------------------------------- | | `TurboDocxException` | varies | Base exception for all SDK errors | | `AuthenticationException` | 401 | Invalid or missing API credentials | | `ValidationException` | 400 | Invalid request parameters | | `NotFoundException` | 404 | Document or resource not found | | `RateLimitException` | 429 | Too many requests | | `NetworkException` | - | Network connectivity issues | All exceptions extend `TurboDocxException` and include: - `getMessage()` - Human-readable error message - `statusCode` - HTTP status code (if applicable) - `errorCode` - Error code string (e.g., 'AUTHENTICATION_ERROR') --- ## PHP Types ### Enums The SDK uses PHP 8.1+ enums for type safety: ```php // Field types enum SignatureFieldType: string { case SIGNATURE = 'signature'; case INITIAL = 'initial'; case DATE = 'date'; case TEXT = 'text'; case FULL_NAME = 'full_name'; case FIRST_NAME = 'first_name'; case LAST_NAME = 'last_name'; case EMAIL = 'email'; case TITLE = 'title'; case COMPANY = 'company'; case CHECKBOX = 'checkbox'; } // Field placement enum FieldPlacement: string { case REPLACE = 'replace'; case BEFORE = 'before'; case AFTER = 'after'; case ABOVE = 'above'; case BELOW = 'below'; } // Document status enum DocumentStatus: string { case DRAFT = 'draft'; case SETUP_COMPLETE = 'setup_complete'; case REVIEW_READY = 'review_ready'; case UNDER_REVIEW = 'under_review'; case COMPLETED = 'completed'; case VOIDED = 'voided'; } ``` ### Readonly Classes The SDK uses readonly classes with typed properties: ```php final class Recipient { public function __construct( public string $name, public string $email, public int $signingOrder ) {} } final class Field { public function __construct( public SignatureFieldType $type, public string $recipientEmail, public ?int $page = null, public ?int $x = null, public ?int $y = null, public ?int $width = null, public ?int $height = null, public ?TemplateConfig $template = null, public ?string $defaultValue = null, public bool $isMultiline = false, public bool $isReadonly = false, public bool $required = false, public ?string $backgroundColor = null ) {} } ``` ### Request Objects ```php final class SendSignatureRequest { public function __construct( public array $recipients, // Recipient[] public array $fields, // Field[] public ?string $file = null, public ?string $fileName = null, public ?string $fileLink = null, public ?string $deliverableId = null, public ?string $templateId = null, public ?string $documentName = null, public ?string $documentDescription = null, public ?string $senderName = null, public ?string $senderEmail = null, public ?array $ccEmails = null ) {} } ``` :::info File Source (Conditional) Exactly one file source is required: `file`, `fileLink`, `deliverableId`, or `templateId`. ::: --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[Request Body Reference](/docs/TurboSign/API%20Signatures#request-body-multipartform-data)** - Complete request body parameters, file sources, and multipart/form-data structure - **[Recipients Reference](/docs/TurboSign/API%20Signatures#recipients-reference)** - Recipient properties, signing order, metadata, and configuration options - **[Field Types Reference](/docs/TurboSign/API%20Signatures#field-types-reference)** - All available field types (signature, date, text, checkbox, etc.) with properties and behaviors - **[Field Positioning Methods](/docs/TurboSign/API%20Signatures#field-positioning-methods)** - Template-based vs coordinate-based positioning, anchor configuration, and best practices --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) - [Packagist Package](https://packagist.org/packages/turbodocx/sdk) - [API Reference](/docs/TurboSign/API%20Signatures) - [Webhook Configuration](/docs/TurboSign/Webhooks) --- # Python SDK The official TurboDocx SDK for Python applications. Build document generation and digital signature workflows with async/await patterns and comprehensive error handling. Available on PyPI as `turbodocx-sdk`. ## Installation ```bash pip install turbodocx-sdk ``` ```bash poetry add turbodocx-sdk ``` ```bash pipenv install turbodocx-sdk ``` ## Requirements - Python 3.8+ - `httpx` (installed automatically) - `pydantic` (installed automatically) --- ## Configuration ```python from turbodocx_sdk import TurboSign # Configure globally (recommended) TurboSign.configure( api_key=os.environ["TURBODOCX_API_KEY"], # Required: Your TurboDocx API key org_id=os.environ["TURBODOCX_ORG_ID"], # Required: Your organization ID sender_email="contracts@yourcompany.com", # Required: Reply-to address for signature emails sender_name="Your Company", # Recommended: Sender name shown in emails # base_url="https://api.turbodocx.com" # Optional: Override base URL ) ``` :::tip Authentication Authenticate using `api_key`. API keys are recommended for server-side applications. ::: ### Environment Variables ```bash # .env TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here TURBODOCX_SENDER_EMAIL=contracts@yourcompany.com TURBODOCX_SENDER_NAME=Your Company ``` :::warning API Credentials Required `api_key` and `org_id` are **required** for all API requests. TurboSign additionally **requires `sender_email`** (set it on `configure()`, per call, or via the `TURBODOCX_SENDER_EMAIL` environment variable) — `configure()` raises a `ValidationError` without it. `sender_name` is optional but strongly recommended. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Send a Document for Signature ```python from turbodocx_sdk import TurboSign TurboSign.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], sender_email="contracts@acme.com", sender_name="Acme Corp", ) async def send_contract(): result = await TurboSign.send_signature( recipients=[ {"name": "Alice Smith", "email": "alice@example.com", "signingOrder": 1}, {"name": "Bob Johnson", "email": "bob@example.com", "signingOrder": 2} ], fields=[ # Alice's signature {"type": "signature", "page": 1, "x": 100, "y": 650, "width": 200, "height": 50, "recipientEmail": "alice@example.com"}, {"type": "date", "page": 1, "x": 320, "y": 650, "width": 100, "height": 30, "recipientEmail": "alice@example.com"}, # Bob's signature {"type": "signature", "page": 1, "x": 100, "y": 720, "width": 200, "height": 50, "recipientEmail": "bob@example.com"}, {"type": "date", "page": 1, "x": 320, "y": 720, "width": 100, "height": 30, "recipientEmail": "bob@example.com"} ], file_link="https://www.turbodocx.com/examples/turbodocx.pdf", document_name="Service Agreement", sender_name="Acme Corp", sender_email="contracts@acme.com", ) print("Result:", json.dumps(result, indent=2)) asyncio.run(send_contract()) ``` ### Using Template-Based Fields ```python async def send_with_template(): result = await TurboSign.send_signature( recipients=[{"name": "Alice Smith", "email": "alice@example.com", "signingOrder": 1}], fields=[ { "type": "signature", "recipientEmail": "alice@example.com", "template": { "anchor": "{SIGNATURE_ALICE}", "placement": "replace", "size": {"width": 200, "height": 50}, }, }, { "type": "date", "recipientEmail": "alice@example.com", "template": { "anchor": "{DATE_ALICE}", "placement": "replace", "size": {"width": 100, "height": 30}, }, }, ], file_link="https://www.turbodocx.com/examples/turbodocx.pdf", ) print("Result:", json.dumps(result, indent=2)) asyncio.run(send_with_template()) ``` :::info Template Anchors Required **Important:** The document file must contain the anchor text (e.g., `{SIGNATURE_ALICE}`, `{DATE_ALICE}`) that you reference in your fields. If the anchors don't exist in the document, the API will return an error. ::: --- ## File Input Methods TurboSign supports four different ways to provide document files: :::note Async context The examples below use `await`, so they must run inside an `async` function (call them with `asyncio.run(...)`). See the [Send a Document for Signature](#send-a-document-for-signature) Quick Start for the full runnable form. ::: ### 1. File Upload (bytes) ```python with open("./contract.pdf", "rb") as f: pdf_buffer = f.read() result = await TurboSign.send_signature( file=pdf_buffer, recipients=[ {"name": "John Doe", "email": "john@example.com", "signingOrder": 1}, ], fields=[ { "type": "signature", "page": 1, "x": 100, "y": 650, "width": 200, "height": 50, "recipientEmail": "john@example.com", }, ], ) ``` ### 2. File URL (file_link) ```python result = await TurboSign.send_signature( file_link="https://www.turbodocx.com/examples/turbodocx.pdf", recipients=[ {"name": "John Doe", "email": "john@example.com", "signingOrder": 1}, ], fields=[ { "type": "signature", "page": 1, "x": 100, "y": 650, "width": 200, "height": 50, "recipientEmail": "john@example.com", }, ], ) ``` :::tip When to use file_link Use `file_link` when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files. ::: ### 3. TurboDocx Deliverable ID ```python # Use a previously generated TurboDocx document result = await TurboSign.send_signature( deliverable_id="deliverable-uuid-from-turbodocx", recipients=[ {"name": "John Doe", "email": "john@example.com", "signingOrder": 1}, ], fields=[ { "type": "signature", "page": 1, "x": 100, "y": 650, "width": 200, "height": 50, "recipientEmail": "john@example.com", }, ], ) ``` :::info Integration with TurboDocx `deliverable_id` references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign. ::: ### 4. TurboDocx Template ID ```python # Use a pre-configured TurboSign template result = await TurboSign.send_signature( template_id="template-uuid-from-turbodocx", # Template already contains anchors recipients=[ {"name": "Alice Smith", "email": "alice@example.com", "signingOrder": 1}, ], fields=[ { "type": "signature", "recipientEmail": "alice@example.com", "template": { "anchor": "{SIGNATURE_ALICE}", "placement": "replace", "size": {"width": 200, "height": 50}, }, }, ], ) ``` :::info Integration with TurboDocx `template_id` references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents. ::: --- ## API Reference :::note Async context The snippets below use `await`, so they must run inside an `async` function. For a standalone script, wrap the call and run it with `asyncio.run(...)`, and add `import json` if the snippet calls `json.dumps`: ```python async def main(): result = await TurboSign.get_status("document-uuid") print(json.dumps(result, indent=2)) asyncio.run(main()) ``` ::: ### Configure Configure the SDK with your API credentials and organization settings. ```python TurboSign.configure( api_key: str, # Required: Your TurboDocx API key org_id: str, # Required: Your organization ID sender_email: str, # Required: Reply-to address for signature emails sender_name: str = None, # Recommended: Sender name shown in emails base_url: str = "https://api.turbodocx.com" # Optional: API base URL ) ``` ### Prepare for review Upload a document for preview without sending signature request emails. ```python result = await TurboSign.create_signature_review_link( recipients=[{"name": "John Doe", "email": "john@example.com", "signingOrder": 1}], fields=[{"type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "john@example.com"}], file_link="https://www.turbodocx.com/examples/turbodocx.pdf", document_name="Contract Draft", ) print(result["documentId"]) print(result["previewUrl"]) ``` ### Prepare for signing Upload a document and immediately send signature requests to all recipients. ```python result = await TurboSign.send_signature( recipients=[{"name": "Recipient Name", "email": "recipient@example.com", "signingOrder": 1}], fields=[{"type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "recipient@example.com"}], file_link="https://www.turbodocx.com/examples/turbodocx.pdf", document_name="Service Agreement", sender_name="Your Company", sender_email="sender@company.com", ) print(result["documentId"]) ``` ### Get status Retrieve the current status of a document. ```python result = await TurboSign.get_status("document-uuid") print("Result:", json.dumps(result, indent=2)) ``` ### Download document Download the completed signed document as PDF bytes. ```python pdf_bytes = await TurboSign.download("document-uuid") # Save to file with open("signed-contract.pdf", "wb") as f: f.write(pdf_bytes) ``` ### Void Cancel/void a signature request. ```python result = await TurboSign.void_document("document-uuid", reason="Contract terms changed") ``` ### Resend Resend signature request emails to specific recipients. ```python result = await TurboSign.resend_email("document-uuid", recipient_ids=["recipient-uuid-1", "recipient-uuid-2"]) ``` ### Get audit trail Retrieve the complete audit trail for a document, including all events and actions. ```python result = await TurboSign.get_audit_trail("document-uuid") print("Result:", json.dumps(result, indent=2)) ``` --- ## Error Handling The SDK provides typed error classes for different failure scenarios. All errors extend the base `TurboDocxError` class. ### Error Classes | Error Class | Status Code | Description | | --------------------- | ----------- | ----------------------------------- | | `TurboDocxError` | varies | Base error class for all SDK errors | | `AuthenticationError` | 401 | Invalid or missing API credentials | | `ValidationError` | 400 | Invalid request parameters | | `NotFoundError` | 404 | Document or resource not found | | `RateLimitError` | 429 | Too many requests | | `NetworkError` | - | Network connectivity issues | ### Handling Errors ```python from turbodocx_sdk import ( TurboSign, TurboDocxError, AuthenticationError, ValidationError, NotFoundError, RateLimitError, NetworkError, ) async def send_with_error_handling(): try: result = await TurboSign.send_signature( recipients=[{"name": "John Doe", "email": "john@example.com", "signingOrder": 1}], fields=[{ "type": "signature", "page": 1, "x": 100, "y": 650, "width": 200, "height": 50, "recipientEmail": "john@example.com", }], file_link="https://www.turbodocx.com/examples/turbodocx.pdf", ) except AuthenticationError as e: print(f"Authentication failed: {e}") # Check your API key and org ID except ValidationError as e: print(f"Validation error: {e}") # Check request parameters except NotFoundError as e: print(f"Resource not found: {e}") # Document or recipient doesn't exist except RateLimitError as e: print(f"Rate limited: {e}") # Wait and retry except NetworkError as e: print(f"Network error: {e}") # Check connectivity except TurboDocxError as e: print(f"SDK error: {e}, status_code={e.status_code}, code={e.code}") asyncio.run(send_with_error_handling()) ``` ### Error Properties All errors include these properties: | Property | Type | Description | | ------------- | ------------- | --------------------------------------------------- | | `message` | `str` | Human-readable error description (via `str(error)`) | | `status_code` | `int \| None` | HTTP status code (if applicable) | | `code` | `str \| None` | Machine-readable error code | --- ## Python Types The SDK uses Python type hints with `Dict[str, Any]` for flexible JSON-like structures. ### Importing Types ```python from typing import Dict, List, Any, Optional ``` ### SignatureFieldType String literal values for field types: ```python # Available field type values field_types = [ "signature", "initial", "date", "text", "full_name", "title", "company", "first_name", "last_name", "email", "checkbox", ] ``` ### Recipient Recipient configuration for signature requests:   | Property | Type | Required | Description | | -------------- | ----- | -------- | ------------------------- | | `name` | `str` | Yes | Recipient's full name | | `email` | `str` | Yes | Recipient's email address | | `signingOrder` | `int` | Yes | Signing order (1-indexed) | ```python recipient: Dict[str, Any] = { "name": "John Doe", "email": "john@example.com", "signingOrder": 1 } ``` ### Field Field configuration supporting both coordinate-based and template-based positioning:   | Property | Type | Required | Description | | ----------------- | ------ | -------- | --------------------------------------------------- | | `type` | `str` | Yes | Field type (see SignatureFieldType) | | `recipientEmail` | `str` | Yes | Which recipient fills this field | | `page` | `int` | No\* | Page number (1-indexed) | | `x` | `int` | No\* | X coordinate in pixels | | `y` | `int` | No\* | Y coordinate in pixels | | `width` | `int` | No\* | Field width in pixels | | `height` | `int` | No\* | Field height in pixels | | `defaultValue` | `str` | No | Default value (for checkbox: `"true"` or `"false"`) | | `isMultiline` | `bool` | No | Enable multiline text | | `isReadonly` | `bool` | No | Make field read-only (pre-filled) | | `required` | `bool` | No | Whether field is required | | `backgroundColor` | `str` | No | Background color (hex, rgb, or named) | | `template` | `Dict` | No | Template anchor configuration | \*Required when not using template anchors **Template Configuration:** | Property | Type | Required | Description | | --------------- | ------ | -------- | ---------------------------------------------------------------- | | `anchor` | `str` | Yes | Text anchor pattern like `{TagName}` | | `placement` | `str` | Yes | `"replace"` \| `"before"` \| `"after"` \| `"above"` \| `"below"` | | `size` | `Dict` | Yes | `{ "width": int, "height": int }` | | `offset` | `Dict` | No | `{ "x": int, "y": int }` | | `caseSensitive` | `bool` | No | Case sensitive search (default: False) | | `useRegex` | `bool` | No | Use regex for anchor/searchText (default: False) | ```python field: Dict[str, Any] = { "type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "john@example.com" } ``` ### Request Parameters Request configuration for `create_signature_review_link` and `send_signature` methods:   | Parameter | Type | Required | Description | | ---------------------- | ------------ | ----------- | ------------------------------ | | `recipients` | `List[Dict]` | Yes | Recipients who will sign | | `fields` | `List[Dict]` | Yes | Signature fields configuration | | `file` | `bytes` | Conditional | PDF file content as bytes | | `file_name` | `str` | No | Original filename (used with `file` bytes) | | `file_link` | `str` | Conditional | URL to document file | | `deliverable_id` | `str` | Conditional | TurboDocx deliverable ID | | `template_id` | `str` | Conditional | TurboDocx template ID | | `document_name` | `str` | No | Document name | | `document_description` | `str` | No | Document description | | `sender_name` | `str` | No | Sender name (overrides the configured value) | | `sender_email` | `str` | No\*\* | Sender / reply-to email (overrides the configured value) | | `cc_emails` | `List[str]` | No | Array of CC email addresses | :::info File Source (Conditional) Exactly one file source is required: `file`, `file_link`, `deliverable_id`, or `template_id`. ::: \*\* `sender_email` is optional per call but **required at the SDK level** for TurboSign: it must be supplied via `configure()`, the `TURBODOCX_SENDER_EMAIL` environment variable, or this per-call parameter, otherwise the SDK raises a `ValidationError`. --- ## Additional Documentation For detailed information about advanced configuration and API concepts, see: ### Core API References - **[Request Body Reference](/docs/TurboSign/API%20Signatures#request-body-multipartform-data)** - Complete request body parameters, file sources, and multipart/form-data structure - **[Recipients Reference](/docs/TurboSign/API%20Signatures#recipients-reference)** - Recipient properties, signing order, metadata, and configuration options - **[Field Types Reference](/docs/TurboSign/API%20Signatures#field-types-reference)** - All available field types (signature, date, text, checkbox, etc.) with properties and behaviors - **[Field Positioning Methods](/docs/TurboSign/API%20Signatures#field-positioning-methods)** - Template-based vs coordinate-based positioning, anchor configuration, and best practices --- ## Resources - [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) - [PyPI Package](https://pypi.org/project/turbodocx-sdk/) - [API Reference](/docs/TurboSign/API%20Signatures) - [Webhook Configuration](/docs/TurboSign/Webhooks) --- # TurboQuote Go SDK The official TurboDocx TurboQuote SDK for Go applications. Create quotes, attach line items and bundles, send proposals to customers, download PDFs, and manage your full product catalog — products, bundles, price books, companies, contacts, and quote templates — all with idiomatic Go patterns, context support, and typed errors. Available as `github.com/TurboDocx/SDK/packages/go-sdk`. :::info What is TurboQuote? TurboQuote is TurboDocx's CPQ (Configure, Price, Quote) module. It lets your application generate professional, branded quote documents and send them to customers for acceptance or rejection, with full lifecycle management (draft, sent, accepted, declined, voided). For the dashboard UI, quote template configuration, and sending behavior, see the TurboQuote product documentation. ::: ## Installation ```bash go get github.com/TurboDocx/SDK/packages/go-sdk ``` Then import: ```go ``` ## Requirements - Go 1.21 or higher - A TurboDocx API key (`TDX-` prefix) - All methods accept a `context.Context` — pass `context.Background()` for one-offs or your request context inside handlers ## Configuration ```go "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) qc, err := turbodocx.NewQuoteClient(turbodocx.QuoteClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } ``` `NewQuoteClient` does **not** require `SenderEmail` — TurboQuote does not send signature emails, so the sender validation enforced by `NewClientWithConfig` (TurboSign) is skipped here. `OrgID` is required; both `APIKey` and `OrgID` fall back to `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID` environment variables when not set in config. ### Environment Variables ```bash TURBODOCX_API_KEY=your_api_key TURBODOCX_ORG_ID=your_org_id # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com ``` :::caution API Credentials Required Both `APIKey` and `OrgID` are required. To get your credentials, follow the [Get Your Credentials](/docs/SDKs#1-get-your-credentials) steps from the SDKs main page. ::: ## Quick Start ### Full lifecycle: create → add items → send → download PDF ```go package main "context" "fmt" "log" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { ctx := context.Background() qc, err := turbodocx.NewQuoteClient(turbodocx.QuoteClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } // 1. Create a draft quote currency := "USD" termDays := 30 quote, err := qc.CreateQuote(ctx, &turbodocx.CreateQuoteRequest{ Name: "Acme Corp — Q3 Proposal", CompanyID: "company-uuid", ContactID: "contact-uuid", CurrencyCode: ¤cy, TermDays: &termDays, }) if err != nil { log.Fatal(err) } fmt.Printf("Created quote %s (%s)\n", quote.QuoteNumber, quote.ID) // 2. Add a product line item qty := 5 lineItems, err := qc.AddLineItems(ctx, quote.ID, turbodocx.AddLineItemRequest{ ProductID: &[]string{"product-uuid"}[0], ProductName: "Enterprise License", UnitPrice: 500.00, BillingFrequency: "annual", Quantity: &qty, }) if err != nil { log.Fatal(err) } fmt.Printf("Added %d line item(s)\n", len(lineItems)) // 3. Send the quote to the customer sent, err := qc.SendQuote(ctx, quote.ID, &turbodocx.SendQuoteRequest{ CCEmails: []string{"manager@acmecorp.com"}, }) if err != nil { log.Fatal(err) } fmt.Printf("Sent: %s\n", sent.Message) // 4. Download PDF pdfBytes, err := qc.DownloadQuotePdf(ctx, quote.ID) if err != nil { log.Fatal(err) } os.WriteFile("quote.pdf", pdfBytes, 0644) fmt.Printf("PDF saved (%d bytes)\n", len(pdfBytes)) } ``` ### Convenience: CreateAndSend `CreateAndSend` performs the create + add items + send flow in a single call (3-4 sequential API requests under the hood): ```go currency := "USD" resp, err := qc.CreateAndSend(ctx, &turbodocx.CreateAndSendRequest{ Name: "Acme Corp — Quick Proposal", CompanyID: "company-uuid", ContactID: "contact-uuid", CurrencyCode: ¤cy, Items: []turbodocx.AddLineItemRequest{ { ProductName: "Enterprise License", UnitPrice: 500.00, BillingFrequency: "annual", }, }, Send: &turbodocx.SendQuoteRequest{}, }) if err != nil { log.Fatal(err) } fmt.Printf("Quote %s sent\n", resp.Quote.QuoteNumber) ``` ## Method Reference All methods are instance methods on `*turbodocx.QuoteClient`. Construct once, then reuse across goroutines — the client is safe for concurrent use. --- ### Quotes #### ListQuotes Retrieve a paginated list of quotes with optional filters. Returns `*QuoteListResponse` which includes `Results`, `TotalRecords`, and aggregate `Stats` (pipeline totals, win rate, MRR, etc.). ```go limit := 10 statuses := []string{"draft", "sent"} list, err := qc.ListQuotes(ctx, &turbodocx.ListQuotesOptions{ Limit: &limit, Statuses: statuses, }) // list.Results []Quote // list.TotalRecords int // list.Stats.WinRate float64 ``` | Field | Type | Description | |---|---|---| | `Limit` | `*int` | Results per page | | `Offset` | `*int` | Results to skip | | `Query` | `*string` | Search by name or quote number | | `Statuses` | `[]string` | Filter by status (e.g., `"draft"`, `"sent"`, `"accepted"`) | | `CompanyID` | `*string` | Filter by company | | `ContactID` | `*string` | Filter by contact | | `CurrencyCode` | `*string` | Filter by currency | #### CreateQuote ```go quote, err := qc.CreateQuote(ctx, &turbodocx.CreateQuoteRequest{ Name: "New Proposal", CompanyID: "company-uuid", ContactID: "contact-uuid", }) ``` Required: `Name`, `CompanyID`, `ContactID`. Returns `*Quote`. #### GetQuote Fetches a quote by ID. The `StatusInfo` field (merged onto the returned `Quote`) describes what transitions are available: `CanSend`, `CanAccept`, `CanDecline`, `CanVoid`, `IsTerminal`. ```go quote, err := qc.GetQuote(ctx, "quote-uuid") if quote.StatusInfo != nil { fmt.Printf("Can send: %v\n", quote.StatusInfo.CanSend) } ``` #### UpdateQuote PATCH semantics — only provided fields are sent. Use the `Clear*` helpers to explicitly null a field: ```go req := &turbodocx.UpdateQuoteRequest{} req.ClearPriceBookID() // sends "priceBookId": null req.ClearValidUntil() // sends "validUntil": null quote, err := qc.UpdateQuote(ctx, "quote-uuid", req) ``` Available null-clear helpers: `ClearPriceBookID`, `ClearValidUntil`, `ClearTaxRate`, `ClearRenewalPeriod`. #### DeleteQuote ```go result, err := qc.DeleteQuote(ctx, "quote-uuid") // result.Message string ``` #### DuplicateQuote Creates a new draft quote as a copy of the specified quote. ```go copy, err := qc.DuplicateQuote(ctx, "quote-uuid") ``` #### ApplyPriceBook Applies a price book to a quote, adjusting line item prices to match book pricing. ```go resp, err := qc.ApplyPriceBook(ctx, "quote-uuid", "pricebook-uuid") // resp.UpdatedCount int — items updated // resp.SkippedCount int — items not in price book // resp.QuoteResult Quote ``` #### RemovePriceBook Removes the applied price book, reverting line items to catalog prices. ```go quote, err := qc.RemovePriceBook(ctx, "quote-uuid") ``` --- ### Quote Status Transitions #### SendQuote Moves the quote from `draft` to `sent` and emails the proposal to the contact. ```go sent, err := qc.SendQuote(ctx, "quote-uuid", &turbodocx.SendQuoteRequest{ CCEmails: []string{"cc@example.com"}, ValidUntil: &[]string{"2026-09-01"}[0], }) // sent.QuoteResult Quote // sent.Message string ``` #### SendQuoteWithDeliverable Sends the quote with a TurboDocx-generated document (e.g., a proposal PDF) attached as a signature document. ```go resp, err := qc.SendQuoteWithDeliverable(ctx, "quote-uuid", &turbodocx.SendQuoteWithDeliverableRequest{ DeliverableID: "deliverable-uuid", MergePosition: "after", // "before" | "after" CCEmails: []string{"cc@example.com"}, }) // resp.DocumentID string — TurboSign document ID for tracking ``` #### DeclineQuote ```go quote, err := qc.DeclineQuote(ctx, "quote-uuid", &turbodocx.DeclineQuoteRequest{ Reason: "Budget constraints for this quarter", }) ``` #### VoidQuote ```go quote, err := qc.VoidQuote(ctx, "quote-uuid", &turbodocx.VoidQuoteRequest{ Reason: "Replaced by updated proposal", }) ``` #### HandleExpiredQuote Handles a `sent` quote that has passed its `validUntil` date. `Action` is one of `"resend"`, `"extend"`, or `"void"`. ```go quote, err := qc.HandleExpiredQuote(ctx, "quote-uuid", &turbodocx.HandleExpiredQuoteRequest{ Action: "extend", Reason: "Customer requested more time", NewValidUntil: "2026-10-01", }) ``` #### DownloadQuotePdf Returns the raw PDF bytes. Write directly to a file or stream to a response. ```go pdfBytes, err := qc.DownloadQuotePdf(ctx, "quote-uuid") if err != nil { log.Fatal(err) } os.WriteFile("proposal.pdf", pdfBytes, 0644) ``` --- ### Line Items Line items belong to a quote. Products are added individually; bundles use a separate endpoint. #### ListLineItems ```go lineItemType := "product" list, err := qc.ListLineItems(ctx, "quote-uuid", &turbodocx.ListLineItemsOptions{ LineItemType: &lineItemType, }) // list.Results []LineItem // list.TotalRecords int ``` #### AddLineItems Accepts one or more `AddLineItemRequest` values (variadic). Returns `[]LineItem`. ```go qty := 3 disc := 10.0 items, err := qc.AddLineItems(ctx, "quote-uuid", turbodocx.AddLineItemRequest{ ProductName: "Support Plan", UnitPrice: 200.00, BillingFrequency: "monthly", Quantity: &qty, DiscountPercent: &disc, }, ) ``` #### AddBundleLineItems ```go items, err := qc.AddBundleLineItems(ctx, "quote-uuid", turbodocx.AddBundleLineItemRequest{ BundleID: "bundle-uuid", BundleName: "Starter Bundle", }, ) ``` #### UpdateLineItem PATCH semantics. Use `Clear*` helpers for explicit nulls: `ClearCost`, `ClearCategoryID`, `ClearCategoryName`, `ClearProductSku`, `ClearProductDescription`, `ClearDisplayOrder`. ```go newPrice := 180.00 item, err := qc.UpdateLineItem(ctx, "quote-uuid", "item-uuid", &turbodocx.UpdateLineItemRequest{ UnitPrice: &newPrice, }) ``` #### RemoveLineItem ```go result, err := qc.RemoveLineItem(ctx, "quote-uuid", "item-uuid") ``` --- ### Products The product catalog powers line item selection in quotes. #### ListProducts ```go list, err := qc.ListProducts(ctx, &turbodocx.ListProductsOptions{ Query: &[]string{"license"}[0], }) // list.Results []Product // list.TotalProducts int // list.CatalogValue float64 ``` #### CreateProduct ```go price := 99.99 product, err := qc.CreateProduct(ctx, &turbodocx.CreateProductRequest{ Name: "Starter License", ListPrice: price, BillingFrequency: "monthly", CategoryID: "category-uuid", }) ``` For products with images, set `Images []ProductImageInput` (supports `FilePath` or `Data` bytes) — the SDK automatically uses multipart upload: ```go product, err := qc.CreateProduct(ctx, &turbodocx.CreateProductRequest{ Name: "Starter License", ListPrice: 99.99, BillingFrequency: "monthly", CategoryID: "category-uuid", Images: []turbodocx.ProductImageInput{ {FilePath: "/path/to/logo.png"}, }, }) ``` #### GetProduct / DeleteProduct / DuplicateProduct ```go product, err := qc.GetProduct(ctx, "product-uuid") result, err := qc.DeleteProduct(ctx, "product-uuid") copy, err := qc.DuplicateProduct(ctx, "product-uuid") ``` #### UpdateProduct PATCH semantics with `Clear*` helpers: `ClearCost`, `ClearSku`, `ClearDescription`, `ClearDetailedSpecification`, `ClearInternalNotes`. Supports image uploads via `Images`. ```go newPrice := 109.99 product, err := qc.UpdateProduct(ctx, "product-uuid", &turbodocx.UpdateProductRequest{ ListPrice: &newPrice, }) ``` #### GetProductPrimaryImages Returns a `ProductPrimaryImagesResponse` (`map[string]*ProductImage`) keyed by product ID. ```go images, err := qc.GetProductPrimaryImages(ctx, []string{"product-uuid-1", "product-uuid-2"}) if img, ok := images["product-uuid-1"]; ok && img != nil { fmt.Println(img.FileName) } ``` --- ### Bundles Bundles group products into a single sellable unit. ```go // Create bundle, err := qc.CreateBundle(ctx, &turbodocx.CreateBundleRequest{ Name: "Starter Pack", CategoryID: "category-uuid", Items: []turbodocx.BundleItemInput{ { ProductID: "product-uuid", UnitPrice: 100.00, BillingFrequency: "monthly", }, }, }) // Get bundle, err = qc.GetBundle(ctx, "bundle-uuid") // Update bundle, err = qc.UpdateBundle(ctx, "bundle-uuid", &turbodocx.UpdateBundleRequest{ Name: &[]string{"Updated Pack"}[0], }) // Delete result, err := qc.DeleteBundle(ctx, "bundle-uuid") // Duplicate copy, err := qc.DuplicateBundle(ctx, "bundle-uuid") ``` ```go list, err := qc.ListBundles(ctx, &turbodocx.ListBundlesOptions{ ShowInCatalog: &[]bool{true}[0], }) // list.Results []Bundle // list.TotalBundles int // list.CatalogValue float64 ``` `UpdateBundle` has `Clear*` helpers for nullable fields: `ClearDescription`, `ClearSku`, `ClearCategoryID`. --- ### Price Books Price books apply per-product discounts or fixed prices to quotes in bulk. ```go validFrom := "2026-01-01" discountPercent := 15.0 priceBook, err := qc.CreatePriceBook(ctx, &turbodocx.CreatePriceBookRequest{ Name: "Partner Pricing", PriceBookTypeID: "type-uuid", ValidFrom: validFrom, DiscountPercent: &discountPercent, ProductPricing: []turbodocx.PriceBookProductPricingInput{ { ProductID: "product-uuid", DiscountPercent: &[]float64{15.0}[0], }, }, }) priceBook, err = qc.GetPriceBook(ctx, "pricebook-uuid") priceBook, err = qc.UpdatePriceBook(ctx, "pricebook-uuid", &turbodocx.UpdatePriceBookRequest{ Name: &[]string{"Updated Partner Pricing"}[0], }) result, err := qc.DeletePriceBook(ctx, "pricebook-uuid") copy, err := qc.DuplicatePriceBook(ctx, "pricebook-uuid") ``` ```go // List price books list, err := qc.ListPriceBooks(ctx, &turbodocx.ListPriceBooksOptions{ ShowInQuoteBuilder: &[]bool{true}[0], }) // list.Results []PriceBook // list.DefaultPriceBookName *string // List products within a price book products, err := qc.ListPriceBookProducts(ctx, "pricebook-uuid", &turbodocx.ListPriceBookProductsOptions{ Limit: &[]int{20}[0], }) // products.Results []PriceBookProductPricing — each entry has DiscountPercent + FinalPrice ``` `UpdatePriceBook` has `Clear*` helpers: `ClearDescription`, `ClearValidTo`. --- ### Companies Companies are organizations you send quotes to. Each company must have at least one contact. ```go phone := "+1-555-0100" company, err := qc.CreateCompany(ctx, &turbodocx.CreateCompanyRequest{ Name: "Acme Corporation", Phone: &phone, Contacts: []turbodocx.CreateCompanyContactInput{ {Name: "Jane Smith", Email: "jane@acmecorp.com"}, }, }) company, err = qc.GetCompany(ctx, "company-uuid") company, err = qc.UpdateCompany(ctx, "company-uuid", &turbodocx.UpdateCompanyRequest{ Name: &[]string{"Acme Corp (Updated)"}[0], }) result, err := qc.DeleteCompany(ctx, "company-uuid") ``` ```go // List companies list, err := qc.ListCompanies(ctx, &turbodocx.ListCompaniesOptions{ Query: &[]string{"acme"}[0], }) // List contacts under a company contacts, err := qc.ListCompanyContacts(ctx, "company-uuid", &turbodocx.PaginationParams{ Limit: &[]int{10}[0], }) // contacts.Results []Contact ``` `UpdateCompany` has `Clear*` helpers: `ClearPhone`, `ClearCity`, `ClearState`, `ClearCountry`, `ClearIndustryID`. --- ### Contacts Contacts are individuals at a company. A quote is addressed to a specific contact. ```go // Create contact, err := qc.CreateContact(ctx, &turbodocx.CreateContactRequest{ Name: "Jane Smith", CompanyID: "company-uuid", Email: &[]string{"jane@acmecorp.com"}[0], }) // Update contact, err = qc.UpdateContact(ctx, "contact-uuid", &turbodocx.UpdateContactRequest{ Name: &[]string{"Jane M. Smith"}[0], }) // Delete result, err := qc.DeleteContact(ctx, "contact-uuid") // List (with optional companyId filter) list, err := qc.ListContacts(ctx, &turbodocx.ListContactsOptions{ CompanyID: &[]string{"company-uuid"}[0], }) ``` :::note No GetContact There is no `GetContact(id)` — the backend has no `GET /v1/contacts/:id` endpoint. Use `ListContacts` with a `CompanyID` filter to find contacts for a company, or use `ListCompanyContacts`. ::: `UpdateContact` has `Clear*` helpers: `ClearEmail`, `ClearPhone`, `ClearTitle`. --- ### Quote Templates Quote templates control the branding and layout of sent quote emails and the customer-facing quote page (logo, colors, footer text, terms, sender info). #### GetTemplate Returns the active (default) quote template for the org. Use this for the most common case. ```go tmpl, err := qc.GetTemplate(ctx) fmt.Printf("Primary color: %s\n", tmpl.PrimaryColor) ``` #### GetTemplateByID Retrieves a specific template by ID when you have multiple templates. ```go tmpl, err := qc.GetTemplateByID(ctx, "template-uuid") ``` #### ListTemplates / CreateTemplate / UpdateTemplate / DeleteTemplate ```go list, err := qc.ListTemplates(ctx, &turbodocx.PaginationParams{ Limit: &[]int{5}[0], }) logoURL := "https://cdn.example.com/logo.png" tmpl, err := qc.CreateTemplate(ctx, &turbodocx.CreateQuoteTemplateRequest{ LogoURL: &logoURL, PrimaryColor: &[]string{"#0066CC"}[0], SenderName: &[]string{"Sales Team"}[0], }) tmpl, err = qc.UpdateTemplate(ctx, "template-uuid", &turbodocx.UpdateQuoteTemplateRequest{ PrimaryColor: &[]string{"#003399"}[0], }) result, err := qc.DeleteTemplate(ctx, "template-uuid") ``` `UpdateQuoteTemplateRequest` has `Clear*` helpers: `ClearLogoURL`, `ClearDisclaimer`, `ClearTermsAndConditions`, `ClearClosingMessage`, `ClearSenderName`, `ClearSenderPhone`, `ClearContactEmail`. --- ### Types (Categories) Types are shared category records used by products, bundles, price books, and companies. A single `CategoryType` field distinguishes their role. | `CategoryType` | Used for | |---|---| | `product_category` | Product and line item categories | | `pricebook_type` | Price book type classification | | `company_industry` | Company industry tags | | `bundle_category` | Bundle categories | ```go catType := turbodocx.CategoryTypeProductCategory qType, err := qc.CreateType(ctx, &turbodocx.CreateQuoteTypeRequest{ Name: "Software", CategoryType: catType, }) qType, err = qc.UpdateType(ctx, "type-uuid", &turbodocx.UpdateQuoteTypeRequest{ Name: &[]string{"Software & SaaS"}[0], }) result, err := qc.DeleteType(ctx, "type-uuid") // List with usage info includeUsage := true list, err := qc.ListTypes(ctx, &turbodocx.ListTypesOptions{ CategoryType: &[]string{"product_category"}[0], IncludeUsage: &includeUsage, }) // list.Results[i].Usage.UsageCount int ``` :::note No GetType There is no `GetType(id)` — the backend has no `GET /v1/types/:id` endpoint. Use `ListTypes` with a `CategoryType` filter. ::: --- ## Enums and Constants | Type | Values | |---|---| | `QuoteStatus` | `draft`, `pending_approval`, `sent`, `accepted`, `declined`, `voided` | | `BillingFrequency` | `monthly`, `quarterly`, `annual`, `one-time` | | `LineItemType` | `product`, `bundle` | | `RenewalPeriod` | `weekly`, `monthly`, `quarterly`, `annually` | | `Currency` | `USD`, `EUR`, `GBP`, `CAD`, `AUD`, `INR` | | `CategoryType` | `product_category`, `pricebook_type`, `company_industry`, `bundle_category` | | `BundleItemStatus` | `active`, `product_deleted`, `product_unavailable`, `currency_mismatch` | | `DiscountType` | `percent`, `amount` | --- ## Null-Clear Semantics (PATCH) Go's zero value is indistinguishable from "not set" at the JSON level, so `UpdateQuoteRequest` and related PATCH request types use a private `nullFields` map to track fields the caller explicitly wants to set to `null`. Call the `Clear*` method on the request before passing it: ```go req := &turbodocx.UpdateQuoteRequest{} req.ClearTaxRate() // sends "taxRate": null (removes tax from quote) req.ClearRenewalPeriod() // sends "renewalPeriod": null // Name left unset — omitted from payload entirely quote, err := qc.UpdateQuote(ctx, "quote-uuid", req) ``` Types with `Clear*` helpers: | Type | Clearable fields | |---|---| | `UpdateQuoteRequest` | `PriceBookID`, `ValidUntil`, `TaxRate`, `RenewalPeriod` | | `UpdateLineItemRequest` | `Cost`, `CategoryID`, `CategoryName`, `ProductSku`, `ProductDescription`, `DisplayOrder` | | `UpdateProductRequest` | `Cost`, `Sku`, `Description`, `DetailedSpecification`, `InternalNotes` | | `UpdatePriceBookRequest` | `Description`, `ValidTo` | | `UpdateBundleRequest` | `Description`, `Sku`, `CategoryID` | | `UpdateCompanyRequest` | `Phone`, `City`, `State`, `Country`, `IndustryID` | | `UpdateContactRequest` | `Email`, `Phone`, `Title` | | `UpdateQuoteTemplateRequest` | `LogoURL`, `Disclaimer`, `TermsAndConditions`, `ClosingMessage`, `SenderName`, `SenderPhone`, `ContactEmail` | --- ## Error Handling ```go _, err := qc.CreateQuote(ctx, req) if err != nil { var valErr *turbodocx.ValidationError var auth *turbodocx.AuthenticationError var authz *turbodocx.AuthorizationError var nf *turbodocx.NotFoundError var rate *turbodocx.RateLimitError var netErr *turbodocx.NetworkError var tdx *turbodocx.TurboDocxError switch { case errors.As(err, &valErr): // 400 — invalid request body (missing required field, bad enum value, etc.) log.Printf("Validation: %s", valErr.Message) case errors.As(err, &auth): // 401 — missing or invalid API key case errors.As(err, &authz): // 403 — key valid but lacks required permissions case errors.As(err, &nf): // 404 — quote, product, bundle, etc. not found case errors.As(err, &rate): // 429 — back off and retry case errors.As(err, &netErr): // request never reached the server case errors.As(err, &tdx): log.Printf("API error %d: %s", tdx.StatusCode, tdx.Message) default: log.Fatal(err) } } ``` ### Error Types | Status | Type | When | |---|---|---| | 400 | `*ValidationError` | Bad request body, missing required field, invalid enum | | 401 | `*AuthenticationError` | Missing or invalid API key | | 403 | `*AuthorizationError` | Key valid but lacks required permissions | | 404 | `*NotFoundError` | Quote, product, bundle, company, etc. not found | | 429 | `*RateLimitError` | Rate limit exceeded | | — | `*NetworkError` | DNS failure, refused connection, timeout | --- ## See Also - [TurboQuote JavaScript / TypeScript SDK](/docs/SDKs/quote-javascript) — same API, JS/TS idioms - [TurboQuote Python SDK](/docs/SDKs/quote-python) — same API, Python idioms - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) - [TurboSign Go SDK](/docs/SDKs/go) — sending documents for e-signature - [Deliverable Go SDK](/docs/SDKs/deliverable-go) — generating documents from templates - [TurboWebhooks Go SDK](/docs/SDKs/webhooks-go) — real-time event delivery - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages --- # TurboQuote Java SDK The official TurboDocx TurboQuote SDK for Java applications. Create and send sales quotes, manage line items, products, bundles, and price books — all from Java 11+. Distributed as `com.turbodocx:turbodocx-sdk` on Maven Central (same artifact as TurboSign and TurboWebhooks). :::info What is TurboQuote? TurboQuote is TurboDocx's CPQ (Configure, Price, Quote) module. Build a product catalog, assemble quotes with line items, apply price book discounts, and send branded proposals to contacts — with optional TurboSign e-signature delivery via `sendQuoteWithDeliverable`. No `senderEmail` is required; TurboQuote handles its own email delivery. ::: ## Installation ```xml com.turbodocx turbodocx-sdk 0.4.0 ``` ```groovy implementation 'com.turbodocx:turbodocx-sdk:0.4.0' ``` ```kotlin implementation("com.turbodocx:turbodocx-sdk:0.4.0") ``` Then import: ```java ``` ## Requirements - Java 11 or higher - OkHttp 4.x (included transitively) - Gson 2.x (included transitively) - A TurboDocx API key (`TDX-` prefix) — no administrator role required ## Configuration ```java TurboQuoteClient client = new TurboQuoteClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .build(); TurboQuote tq = client.turboQuote(); ``` `TurboQuoteClient.Builder` does **not** require `senderEmail` — TurboQuote sends quote emails itself via its own send flow, so the sender validation enforced by `TurboDocxClient` for TurboSign is skipped here. `orgId` is required. Construct `TurboQuoteClient` once and reuse `tq` across the lifetime of your application. ### Environment Variables ```bash TURBODOCX_API_KEY=your_api_key TURBODOCX_ORG_ID=your_org_id # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com ``` :::caution API Credentials Required Both `apiKey` and `orgId` are required. To get your credentials, follow the [Get Your Credentials](/docs/SDKs#1-get-your-credentials) steps from the SDKs main page. ::: ## Quick Start ### 1. Create a company, quote, and add line items ```java public class QuoteLifecycle { public static void main(String[] args) throws Exception { TurboQuoteClient client = new TurboQuoteClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .build(); TurboQuote tq = client.turboQuote(); // Step 1: Create a company with an initial contact CreateCompanyContactInput contact = new CreateCompanyContactInput(); contact.setName("Alice Buyer"); contact.setEmail("alice@example.com"); CreateCompanyRequest companyReq = new CreateCompanyRequest(); companyReq.setName("Acme Corp"); companyReq.setContacts(Arrays.asList(contact)); Company company = tq.createCompany(companyReq); ContactListResponse contacts = tq.listCompanyContacts(company.getId()); String contactId = contacts.getResults().get(0).getId(); // Step 2: Create a quote CreateQuoteRequest quoteReq = new CreateQuoteRequest(); quoteReq.setName("Q1 Software License"); quoteReq.setCompanyId(company.getId()); quoteReq.setContactId(contactId); quoteReq.setTermDays(30); quoteReq.setCurrency(Currency.USD); Quote quote = tq.createQuote(quoteReq); System.out.println("Quote: " + quote.getId() + " status=" + quote.getStatus()); // Step 3: Add a line item AddLineItemRequest item = new AddLineItemRequest(); item.setProductName("Enterprise Software License"); item.setUnitPrice(1200.00); item.setQuantity(3.0); item.setBillingFrequency("annual"); item.setDiscountType(DiscountType.PERCENT); item.setDiscountPercent(10.0); List lineItems = tq.addLineItems(quote.getId(), item); System.out.println("Added " + lineItems.size() + " line item(s)"); // Step 4: Send the quote SendQuoteResponse sent = tq.sendQuote(quote.getId()); System.out.println("Sent. Status: " + sent.getQuote().getStatus()); // Step 5: Download the PDF byte[] pdf = tq.downloadQuotePdf(quote.getId()); Files.write(Paths.get("quote.pdf"), pdf); System.out.println("PDF saved (" + pdf.length + " bytes)"); } } ``` ### 2. Create and send in one call `createAndSend` is a convenience method that creates the quote, adds line items and bundle items, and sends it atomically. ```java CreateAndSendRequest req = new CreateAndSendRequest(); req.setName("Partner Proposal"); req.setCompanyId(companyId); req.setContactId(contactId); AddLineItemRequest item = new AddLineItemRequest(); item.setProductName("Starter Plan"); item.setUnitPrice(499.00); item.setQuantity(1.0); req.setItems(Arrays.asList(item)); // req.setSend(...) to configure send options, or omit to use defaults CreateAndSendResponse result = tq.createAndSend(req); System.out.println("Quote created and sent: " + result.getQuote().getId()); ``` ### 3. Apply a price book, then send with a deliverable ```java // Apply a price book to recalculate line item prices ApplyPriceBookResponse applied = tq.applyPriceBook(quoteId, priceBookId); System.out.println("Updated: " + applied.getUpdatedCount() + ", Skipped: " + applied.getSkippedCount()); // Send with a TurboDocx deliverable attached SendQuoteWithDeliverableRequest sendReq = new SendQuoteWithDeliverableRequest(); sendReq.setDeliverableId("your-deliverable-id"); sendReq.setMergePosition("end"); SendQuoteWithDeliverableResponse sendResp = tq.sendQuoteWithDeliverable(quoteId, sendReq); System.out.println("Document ID: " + sendResp.getDocumentId()); ``` ## Method Reference All methods are instance methods on `com.turbodocx.TurboQuote`. Obtain the instance via `client.turboQuote()` from a constructed `TurboQuoteClient`. All methods throw `IOException` and `TurboDocxException` subclasses. --- ### Quotes — CRUD #### `listQuotes` ```java QuoteListResponse listQuotes() QuoteListResponse listQuotes(ListQuotesOptions options) ``` List quotes with optional pagination and filters. Returns a paginated response including stats (totals, counts by status). ```java ListQuotesOptions opts = new ListQuotesOptions(); opts.setLimit(20); opts.setOffset(0); QuoteListResponse list = tq.listQuotes(opts); System.out.println("Total: " + list.getTotalRecords()); list.getResults().forEach(q -> System.out.println(q.getId() + " " + q.getStatus())); ``` #### `createQuote` ```java Quote createQuote(CreateQuoteRequest request) ``` Create a new quote. Returns the created `Quote`. ```java CreateQuoteRequest req = new CreateQuoteRequest(); req.setName("Enterprise Proposal"); req.setCompanyId(companyId); req.setContactId(contactId); req.setCurrency(Currency.USD); req.setTermDays(30); Quote quote = tq.createQuote(req); ``` #### `getQuote` ```java Quote getQuote(String id) ``` Get a quote by ID. Returns the `Quote` with `statusInfo` merged in (expiry dates, status transitions). ```java Quote quote = tq.getQuote(quoteId); System.out.println("Status: " + quote.getStatus()); // quote.getStatusInfo() — expiry/transition metadata ``` #### `updateQuote` ```java Quote updateQuote(String id, UpdateQuoteRequest request) ``` Update an existing quote. Only fields explicitly set on `UpdateQuoteRequest` are patched; unset fields are omitted from the request body. Fields explicitly set to `null` are cleared on the server (e.g., `setValidUntil(null)` clears the expiry date). ```java UpdateQuoteRequest req = new UpdateQuoteRequest(); req.setName("Revised Proposal — Q2"); req.setTermDays(60); Quote updated = tq.updateQuote(quoteId, req); ``` #### `deleteQuote` ```java SuccessResponse deleteQuote(String id) ``` Delete a quote. ```java SuccessResponse resp = tq.deleteQuote(quoteId); System.out.println(resp.getMessage()); ``` #### `duplicateQuote` ```java Quote duplicateQuote(String id) ``` Duplicate a quote (creates a draft copy). ```java Quote copy = tq.duplicateQuote(quoteId); System.out.println("New quote: " + copy.getId()); ``` #### `applyPriceBook` ```java ApplyPriceBookResponse applyPriceBook(String quoteId, String priceBookId) ``` Apply a price book to a quote, recalculating line item prices. Returns `{quote, message, updatedCount, skippedCount}`. ```java ApplyPriceBookResponse resp = tq.applyPriceBook(quoteId, priceBookId); System.out.println("Updated " + resp.getUpdatedCount() + " items."); ``` #### `removePriceBook` ```java Quote removePriceBook(String quoteId) ``` Remove the applied price book from a quote, restoring original line item pricing. ```java Quote quote = tq.removePriceBook(quoteId); ``` #### `downloadQuotePdf` ```java byte[] downloadQuotePdf(String id) ``` Download the quote as a PDF. Returns raw bytes. ```java byte[] pdf = tq.downloadQuotePdf(quoteId); Files.write(Paths.get("quote.pdf"), pdf); ``` --- ### Quotes — Status Transitions #### `sendQuote` ```java SendQuoteResponse sendQuote(String id) SendQuoteResponse sendQuote(String id, SendQuoteRequest request) ``` Send a quote to the contact. Returns `{quote, message}`. Pass `SendQuoteRequest` to configure send options, or omit for defaults. ```java SendQuoteResponse resp = tq.sendQuote(quoteId); System.out.println("Status: " + resp.getQuote().getStatus()); ``` #### `sendQuoteWithDeliverable` ```java SendQuoteWithDeliverableResponse sendQuoteWithDeliverable(String id, SendQuoteWithDeliverableRequest request) ``` Send a quote with a TurboDocx deliverable attached. Returns `{quote, message, documentId}`. ```java SendQuoteWithDeliverableRequest req = new SendQuoteWithDeliverableRequest(); req.setDeliverableId("your-deliverable-id"); req.setMergePosition("end"); // "start" | "end" SendQuoteWithDeliverableResponse resp = tq.sendQuoteWithDeliverable(quoteId, req); System.out.println("Document ID: " + resp.getDocumentId()); ``` #### `declineQuote` ```java Quote declineQuote(String id, DeclineQuoteRequest request) ``` Mark a sent quote as declined. ```java DeclineQuoteRequest req = new DeclineQuoteRequest(); req.setReason("Budget constraints"); Quote declined = tq.declineQuote(quoteId, req); ``` #### `voidQuote` ```java Quote voidQuote(String id, VoidQuoteRequest request) ``` Void a quote (cannot be undone). ```java VoidQuoteRequest req = new VoidQuoteRequest(); req.setReason("Replaced by new proposal"); Quote voided = tq.voidQuote(quoteId, req); ``` #### `handleExpiredQuote` ```java Quote handleExpiredQuote(String id, HandleExpiredQuoteRequest request) ``` Handle an expired sent quote — extend, re-send, or void it. ```java HandleExpiredQuoteRequest req = new HandleExpiredQuoteRequest(); req.setAction("extend"); // "extend" | "resend" | "void" req.setNewValidUntil("2026-12-31"); Quote quote = tq.handleExpiredQuote(quoteId, req); ``` --- ### Line Items #### `listLineItems` ```java LineItemListResponse listLineItems(String quoteId) LineItemListResponse listLineItems(String quoteId, ListLineItemsOptions options) ``` List line items for a quote. ```java LineItemListResponse items = tq.listLineItems(quoteId); items.getResults().forEach(i -> System.out.println(i.getProductName() + " x" + i.getQuantity())); ``` #### `addLineItems` ```java List addLineItems(String quoteId, AddLineItemRequest item) List addLineItems(String quoteId, List items) ``` Add one or more product line items to a quote. A single `AddLineItemRequest` is automatically wrapped. ```java AddLineItemRequest item = new AddLineItemRequest(); item.setProductName("Enterprise License"); item.setUnitPrice(1200.00); item.setQuantity(5.0); item.setBillingFrequency("annual"); List added = tq.addLineItems(quoteId, item); ``` #### `addBundleLineItems` ```java List addBundleLineItems(String quoteId, AddBundleLineItemRequest item) List addBundleLineItems(String quoteId, List items) ``` Add one or more bundle line items to a quote. ```java AddBundleLineItemRequest bundleItem = new AddBundleLineItemRequest(); bundleItem.setBundleId(bundleId); bundleItem.setQuantity(2.0); List added = tq.addBundleLineItems(quoteId, bundleItem); ``` #### `updateLineItem` ```java LineItem updateLineItem(String quoteId, String itemId, UpdateLineItemRequest request) ``` Update a line item on a quote. Only explicitly set fields are patched. ```java UpdateLineItemRequest req = new UpdateLineItemRequest(); req.setQuantity(10.0); req.setDiscountPercent(15.0); LineItem updated = tq.updateLineItem(quoteId, itemId, req); ``` #### `removeLineItem` ```java SuccessResponse removeLineItem(String quoteId, String itemId) ``` Remove a line item from a quote. ```java tq.removeLineItem(quoteId, itemId); ``` --- ### Products | Method | Signature | Returns | |---|---|---| | `listProducts` | `listProducts()` / `listProducts(ListProductsOptions)` | `ProductListResponse` | | `createProduct` | `createProduct(CreateProductRequest)` | `Product` | | `getProduct` | `getProduct(String id)` | `Product` | | `updateProduct` | `updateProduct(String id, UpdateProductRequest)` | `Product` | | `deleteProduct` | `deleteProduct(String id)` | `SuccessResponse` | | `duplicateProduct` | `duplicateProduct(String id)` | `Product` | | `getProductPrimaryImages` | `getProductPrimaryImages(List productIds)` | `Map` | ```java // Create a product CreateProductRequest req = new CreateProductRequest(); req.setName("Pro Platform"); req.setSku("PRO-001"); req.setListPrice(500.00); req.setBillingFrequency("monthly"); Product product = tq.createProduct(req); // Upload with images — pass byte[][] via setImages() req.setImages(new byte[][] { imageBytes }); Product productWithImages = tq.createProduct(req); // Get primary images for a set of products Map images = tq.getProductPrimaryImages( Arrays.asList(product.getId(), anotherProductId)); // images.get(productId) — ProductImage or null ``` :::note Multipart Upload When `CreateProductRequest.getImages()` is non-empty, the SDK automatically switches to multipart form upload with magic-byte MIME type detection (PNG, JPEG, GIF, WebP supported). ::: --- ### Price Books | Method | Signature | Returns | |---|---|---| | `listPriceBooks` | `listPriceBooks()` / `listPriceBooks(ListPriceBooksOptions)` | `PriceBookListResponse` | | `createPriceBook` | `createPriceBook(CreatePriceBookRequest)` | `PriceBook` | | `getPriceBook` | `getPriceBook(String id)` | `PriceBook` | | `updatePriceBook` | `updatePriceBook(String id, UpdatePriceBookRequest)` | `PriceBook` | | `deletePriceBook` | `deletePriceBook(String id)` | `SuccessResponse` | | `duplicatePriceBook` | `duplicatePriceBook(String id)` | `PriceBook` | | `listPriceBookProducts` | `listPriceBookProducts(String id)` / `listPriceBookProducts(String id, ListPriceBookProductsOptions)` | `PriceBookProductListResponse` | ```java // Create a price book with per-product pricing overrides PriceBookProductPricingInput pricing = new PriceBookProductPricingInput(); pricing.setProductId(productId); pricing.setDiscountType(DiscountType.PERCENT); pricing.setDiscountPercent(20.0); CreatePriceBookRequest req = new CreatePriceBookRequest(); req.setName("Partner Discount"); // required req.setPriceBookTypeId(typeId); // required — from a createType(categoryType=PRICEBOOK_TYPE) req.setValidFrom("2025-01-01"); // required req.setDiscountPercent(15.0); // required req.setProductPricing(Arrays.asList(pricing)); req.setShowInQuoteBuilder(true); PriceBook pb = tq.createPriceBook(req); // List products in a price book PriceBookProductListResponse pbProducts = tq.listPriceBookProducts(pb.getId()); System.out.println("Products: " + pbProducts.getTotalRecords()); ``` --- ### Bundles | Method | Signature | Returns | |---|---|---| | `listBundles` | `listBundles()` / `listBundles(ListBundlesOptions)` | `BundleListResponse` | | `createBundle` | `createBundle(CreateBundleRequest)` | `Bundle` | | `getBundle` | `getBundle(String id)` | `Bundle` | | `updateBundle` | `updateBundle(String id, UpdateBundleRequest)` | `Bundle` | | `deleteBundle` | `deleteBundle(String id)` | `SuccessResponse` | | `duplicateBundle` | `duplicateBundle(String id)` | `Bundle` | ```java CreateBundleRequest req = new CreateBundleRequest(); req.setName("Starter Bundle"); // configure items, pricing, etc. Bundle bundle = tq.createBundle(req); ``` --- ### Companies | Method | Signature | Returns | |---|---|---| | `listCompanies` | `listCompanies()` / `listCompanies(ListCompaniesOptions)` | `CompanyListResponse` | | `createCompany` | `createCompany(CreateCompanyRequest)` | `Company` | | `getCompany` | `getCompany(String id)` | `Company` | | `updateCompany` | `updateCompany(String id, UpdateCompanyRequest)` | `Company` | | `deleteCompany` | `deleteCompany(String id)` | `SuccessResponse` | | `listCompanyContacts` | `listCompanyContacts(String companyId)` / `listCompanyContacts(String companyId, PaginationParams)` | `ContactListResponse` | ```java // Create a company — contacts list is required (minimum one contact) CreateCompanyContactInput contact = new CreateCompanyContactInput(); contact.setName("Alice Buyer"); contact.setEmail("alice@example.com"); CreateCompanyRequest req = new CreateCompanyRequest(); req.setName("Acme Corp"); req.setCity("New York"); req.setContacts(Arrays.asList(contact)); Company company = tq.createCompany(req); // List contacts for that company ContactListResponse contacts = tq.listCompanyContacts(company.getId()); String contactId = contacts.getResults().get(0).getId(); ``` --- ### Contacts | Method | Signature | Returns | |---|---|---| | `listContacts` | `listContacts()` / `listContacts(ListContactsOptions)` | `ContactListResponse` | | `createContact` | `createContact(CreateContactRequest)` | `Contact` | | `updateContact` | `updateContact(String id, UpdateContactRequest)` | `Contact` | | `deleteContact` | `deleteContact(String id)` | `SuccessResponse` | :::note No `getContact` There is no `getContact(id)` method — the backend has no `GET /v1/contacts/:id` endpoint. Use `listContacts` with a query filter, or `listCompanyContacts` to retrieve contacts for a known company. ::: ```java CreateContactRequest req = new CreateContactRequest(); req.setName("Bob Partner"); req.setEmail("bob@partner.example.com"); Contact contact = tq.createContact(req); ``` --- ### Quote Templates | Method | Signature | Returns | |---|---|---| | `listTemplates` | `listTemplates()` / `listTemplates(PaginationParams)` | `QuoteTemplateListResponse` | | `getTemplate` | `getTemplate()` | `QuoteTemplate` | | `getTemplateById` | `getTemplateById(String id)` | `QuoteTemplate` | | `createTemplate` | `createTemplate(CreateQuoteTemplateRequest)` | `QuoteTemplate` | | `updateTemplate` | `updateTemplate(String id, UpdateQuoteTemplateRequest)` | `QuoteTemplate` | | `deleteTemplate` | `deleteTemplate(String id)` | `SuccessResponse` | ```java // Get the org's default template (singleton) QuoteTemplate defaultTemplate = tq.getTemplate(); // Get a specific template by ID QuoteTemplate template = tq.getTemplateById(templateId); // List all templates QuoteTemplateListResponse templates = tq.listTemplates(); ``` :::note `getTemplate()` vs `getTemplateById(id)` `getTemplate()` hits `GET /v1/quote-template` (singular) and returns the organization's default template. `getTemplateById(id)` hits `GET /v1/quote-templates/:id` (plural) and returns a specific template by ID. ::: --- ### Types / Categories | Method | Signature | Returns | |---|---|---| | `listTypes` | `listTypes()` / `listTypes(ListTypesOptions)` | `QuoteTypeListResponse` | | `createType` | `createType(CreateQuoteTypeRequest)` | `QuoteType` | | `updateType` | `updateType(String id, UpdateQuoteTypeRequest)` | `QuoteType` | | `deleteType` | `deleteType(String id)` | `SuccessResponse` | Types are used for categorization — `PRICEBOOK_TYPE` and `PRODUCT_CATEGORY` are the two `CategoryType` values. ```java CreateQuoteTypeRequest req = new CreateQuoteTypeRequest(); req.setName("Partner Pricing"); req.setCategoryType(CategoryType.PRICEBOOK_TYPE); QuoteType type = tq.createType(req); ``` :::note No `getType` There is no `getType(id)` method — the backend has no `GET /v1/types/:id` endpoint by design. Use `listTypes` to retrieve all types. ::: --- ### Convenience #### `createAndSend` ```java CreateAndSendResponse createAndSend(CreateAndSendRequest request) ``` Create a quote, add line items and bundle items, and send it — all in one method call. Useful for programmatic quote generation pipelines. ```java AddLineItemRequest item = new AddLineItemRequest(); item.setProductName("Starter License"); item.setUnitPrice(999.00); item.setQuantity(1.0); CreateAndSendRequest req = new CreateAndSendRequest(); req.setName("Quick Proposal"); req.setCompanyId(companyId); req.setContactId(contactId); req.setItems(Arrays.asList(item)); // req.setSend(sendOptions) — optional CreateAndSendResponse result = tq.createAndSend(req); System.out.println("Quote: " + result.getQuote().getId()); ``` --- ## Error Handling ```java try { Quote quote = tq.createQuote(req); } catch (TurboDocxException.ValidationException e) { // 400 — invalid request body (missing required field, bad enum value, etc.) System.err.println("Validation: " + e.getMessage()); } catch (TurboDocxException.AuthenticationException e) { // 401 — bad or revoked API key System.err.println("Auth: " + e.getMessage()); } catch (TurboDocxException.AuthorizationException e) { // 403 — key lacks required role System.err.println("Forbidden: " + e.getMessage()); } catch (TurboDocxException.NotFoundException e) { // 404 — quote, product, company, etc. not found System.err.println("Not found: " + e.getMessage()); } catch (TurboDocxException.RateLimitException e) { // 429 — back off and retry System.err.println("Rate limited: " + e.getMessage()); } catch (TurboDocxException.NetworkException e) { // request never reached the server (DNS, refused, timeout) System.err.println("Network error: " + e.getMessage()); } catch (TurboDocxException e) { // catch-all for any other typed SDK error System.err.println("Error " + e.getStatusCode() + ": " + e.getMessage()); } ``` ### Common Error Codes | Status | Type | When | |---|---|---| | 400 | `TurboDocxException.ValidationException` | Invalid request body, missing required field | | 401 | `TurboDocxException.AuthenticationException` | Missing or invalid API key | | 403 | `TurboDocxException.AuthorizationException` | Valid key without required role | | 404 | `TurboDocxException.NotFoundException` | Quote, product, company, contact, etc. not found | | 429 | `TurboDocxException.RateLimitException` | Rate limit exceeded — back off and retry | ## Runnable End-to-End Examples Three fully runnable examples live in the SDK repo: - **[`TurboQuoteBasic.java`](https://github.com/TurboDocx/SDK/blob/main/packages/java-sdk/examples/TurboQuoteBasic.java)** — full quote lifecycle: create company, create quote, add line items, send, download PDF, clean up. - **[`TurboQuoteProducts.java`](https://github.com/TurboDocx/SDK/blob/main/packages/java-sdk/examples/TurboQuoteProducts.java)** — product and bundle catalog management. - **[`TurboQuotePricebooks.java`](https://github.com/TurboDocx/SDK/blob/main/packages/java-sdk/examples/TurboQuotePricebooks.java)** — price book CRUD, `applyPriceBook`, and optional `sendQuoteWithDeliverable` (set `TURBODOCX_DELIVERABLE_ID` env var). Run any example after exporting `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID`. ## See Also - [TurboQuote JavaScript / TypeScript SDK](/docs/SDKs/quote-javascript) — same API, JS idioms - [TurboQuote Python SDK](/docs/SDKs/quote-python) — same API, Python idioms - [TurboQuote PHP SDK](/docs/SDKs/quote-php) — same API, PHP idioms - [TurboSign Java SDK](/docs/SDKs/java) — sending documents for e-signature - [TurboWebhooks Java SDK](/docs/SDKs/webhooks-java) — receiving signature events - [SDKs Overview](/docs/SDKs) — all SDKs across all languages - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) --- # TurboQuote JavaScript / TypeScript SDK The official TurboDocx TurboQuote SDK for Node.js and browser applications. Build quoting and CPQ (configure-price-quote) workflows: create and send quotes, manage line items, maintain a product and bundle catalog, apply price books, and handle the full quote lifecycle — all with zero runtime dependencies and complete TypeScript types. Available on npm as `@turbodocx/sdk` (same package as TurboSign and TurboWebhooks). :::info What is TurboQuote? TurboQuote is TurboDocx's quoting and CPQ module. Quotes progress through a lifecycle: `draft` → `pending_approval` → `sent` → `accepted` / `declined` / `voided`. Each quote belongs to a company and contact, carries line items (individual products or bundles), and can optionally have a price book applied. Accepted quotes can be merged with a TurboDocx Deliverable (e.g. a contract generated from a template) and sent for e-signature through TurboSign via `sendQuoteWithDeliverable`. ::: ## Installation ```bash npm install @turbodocx/sdk ``` ```bash pnpm add @turbodocx/sdk ``` ```bash yarn add @turbodocx/sdk ``` ## Requirements - Node.js 18 or higher (native `fetch`) - TypeScript 4.7+ (optional, for type checking — declaration files are included) - Zero runtime dependencies — the SDK uses only Node built-ins ## Configuration ```typescript TurboQuote.configure({ apiKey: process.env.TURBODOCX_API_KEY!, orgId: process.env.TURBODOCX_ORG_ID, // optional — falls back to env var }); ``` ```javascript const { TurboQuote } = require('@turbodocx/sdk'); TurboQuote.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, }); ``` :::tip No senderEmail required Unlike TurboSign, `TurboQuote.configure()` does **not** require `senderEmail` or `senderName` — quotes are not sent as signature emails. Only `apiKey` is required; `orgId` is recommended but falls back to `TURBODOCX_ORG_ID`. If you skip `configure()` entirely, the SDK auto-initialises from environment variables on the first method call. ::: ### Environment Variables ```bash TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com ``` ## Quick Start The most common flow: create a quote for a company and contact, add a product line item, then send it. ```typescript TurboQuote.configure({ apiKey: process.env.TURBODOCX_API_KEY!, orgId: process.env.TURBODOCX_ORG_ID, }); // 1. Create a draft quote const quote = await TurboQuote.createQuote({ name: 'Acme Corp — Enterprise Plan', companyId: 'company-uuid', contactId: 'contact-uuid', currency: 'USD', termDays: 30, }); // 2. Add a product line item await TurboQuote.addLineItems(quote.id, { productId: 'product-uuid', productName: 'Enterprise Licence', unitPrice: 1200, billingFrequency: 'annual', quantity: 5, }); // 3. Send the quote const { quote: sentQuote, message } = await TurboQuote.sendQuote(quote.id, { validUntil: '2026-07-31', }); console.log(message); // "Quote sent successfully" console.log(sentQuote.status); // "sent" // 4. Download the PDF const pdf = await TurboQuote.downloadQuotePdf(sentQuote.id); writeFileSync('quote.pdf', Buffer.from(pdf)); ``` ```javascript const { TurboQuote } = require('@turbodocx/sdk'); const { writeFileSync } = require('fs'); TurboQuote.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, }); // 1. Create a draft quote const quote = await TurboQuote.createQuote({ name: 'Acme Corp — Enterprise Plan', companyId: 'company-uuid', contactId: 'contact-uuid', currency: 'USD', termDays: 30, }); // 2. Add a product line item await TurboQuote.addLineItems(quote.id, { productId: 'product-uuid', productName: 'Enterprise Licence', unitPrice: 1200, billingFrequency: 'annual', quantity: 5, }); // 3. Send the quote const { quote: sentQuote, message } = await TurboQuote.sendQuote(quote.id, { validUntil: '2026-07-31', }); console.log(message); // "Quote sent successfully" console.log(sentQuote.status); // "sent" // 4. Download the PDF const pdf = await TurboQuote.downloadQuotePdf(sentQuote.id); writeFileSync('quote.pdf', Buffer.from(pdf)); ``` ### Convenience: createAndSend `createAndSend` combines quote creation, line item addition, and sending into a single call. ```typescript const { quote } = await TurboQuote.createAndSend({ name: 'Acme Corp — Starter', companyId: 'company-uuid', contactId: 'contact-uuid', currency: 'USD', termDays: 30, items: [ { productId: null, productName: 'Setup Fee', unitPrice: 500, billingFrequency: 'one-time' }, { productId: 'product-uuid', productName: 'Monthly Subscription', unitPrice: 99, billingFrequency: 'monthly', quantity: 10 }, ], send: { validUntil: '2026-07-31' }, }); console.log(quote.status); // "sent" ``` --- ## Method Reference All methods are static on the `TurboQuote` class. Configure once, then call on the class directly. ### Quotes #### listQuotes List quotes with optional pagination and filters. Returns totals and pipeline stats alongside results. ```typescript const { results, totalRecords, stats } = await TurboQuote.listQuotes({ limit: 20, offset: 0, statuses: ['draft', 'sent'], // string or string[] companyId: 'company-uuid', currency: 'USD', }); // stats.total, stats.winRate, stats.monthlyRecurringRevenue, ... ``` #### createQuote Create a new quote in `draft` status. ```typescript // Fixed-term quote — termDays 1–3650, no renewalPeriod (0 = one-time, -1 = auto-renewal) const quote = await TurboQuote.createQuote({ name: 'Q3 Renewal', // required companyId: 'company-uuid', // required contactId: 'contact-uuid', // required currency: 'USD', // 'USD'|'EUR'|'GBP'|'CAD'|'AUD'|'INR' termDays: 30, // fixed term in days validUntil: '2026-09-30', taxRate: 8.5, priceBookId: 'pb-uuid', }); // Auto-renewal quote — termDays: -1 REQUIRES renewalPeriod, and renewalPeriod is ONLY valid // when termDays is -1. Pairing renewalPeriod with a fixed term (e.g. termDays: 30) returns a 400. const subscription = await TurboQuote.createQuote({ name: 'Annual Subscription', companyId: 'company-uuid', contactId: 'contact-uuid', currency: 'USD', termDays: -1, // -1 = auto-renewal renewalPeriod: 'annually', // 'weekly'|'monthly'|'quarterly'|'annually' }); ``` #### getQuote Fetch a single quote. The returned object includes a `statusInfo` field with transition flags (`canSend`, `canAccept`, `canDecline`, `canVoid`). ```typescript const quote = await TurboQuote.getQuote('quote-uuid'); console.log(quote.statusInfo?.canSend); // true when status is 'draft' ``` #### updateQuote Patch any combination of quote fields. Pass `null` to clear nullable fields (`renewalPeriod`, `validUntil`, `taxRate`, `priceBookId`). ```typescript const updated = await TurboQuote.updateQuote('quote-uuid', { name: 'Q3 Renewal — Revised', taxRate: null, // clears the tax rate }); ``` #### deleteQuote Soft-delete a quote. ```typescript const { message } = await TurboQuote.deleteQuote('quote-uuid'); ``` #### duplicateQuote Copy a quote (and its line items) into a new draft. ```typescript const copy = await TurboQuote.duplicateQuote('quote-uuid'); ``` #### applyPriceBook Apply a price book to all line items on a quote. Returns the updated quote plus counts of how many items were updated vs skipped. ```typescript const { quote, updatedCount, skippedCount, message } = await TurboQuote.applyPriceBook( 'quote-uuid', 'pricebook-uuid', ); ``` #### removePriceBook Detach the price book from a quote (line item prices are not reverted). ```typescript const quote = await TurboQuote.removePriceBook('quote-uuid'); ``` #### downloadQuotePdf Download the quote as a PDF. Returns raw bytes as an `ArrayBuffer`. ```typescript const pdf = await TurboQuote.downloadQuotePdf('quote-uuid'); writeFileSync('quote.pdf', Buffer.from(pdf)); ``` --- ### Quote Status Transitions #### sendQuote Send a draft quote to the contact. Optionally include CC recipients or set a validity deadline. ```typescript const { quote, message } = await TurboQuote.sendQuote('quote-uuid', { validUntil: '2026-07-31', ccEmails: ['manager@example.com'], }); ``` #### sendQuoteWithDeliverable Send a quote paired with a TurboDocx deliverable (e.g., a contract generated from a template). The deliverable is merged before or after the quote PDF. ```typescript const { quote, message, documentId } = await TurboQuote.sendQuoteWithDeliverable( 'quote-uuid', { deliverableId: 'deliverable-uuid', mergePosition: 'end', // 'beginning' | 'end' ccEmails: ['legal@example.com'], }, ); // documentId — TurboSign document created for the merged PDF ``` #### declineQuote Mark a sent quote as declined (typically called on behalf of the recipient). ```typescript const quote = await TurboQuote.declineQuote('quote-uuid', { reason: 'Budget constraints for this quarter', }); ``` #### voidQuote Void a quote that should no longer be valid. ```typescript const quote = await TurboQuote.voidQuote('quote-uuid', { reason: 'Superseded by revised quote #Q-102', }); ``` #### handleExpiredQuote Handle a quote that has passed its `validUntil` date. Choose to `void` or `decline` it and optionally extend with a new validity date. ```typescript const quote = await TurboQuote.handleExpiredQuote('quote-uuid', { action: 'void', // 'void' | 'decline' reason: 'Expired — re-quoting', newValidUntil: '2026-08-31', }); ``` --- ### Line Items Line items attach products or bundles to a quote, each with a price, quantity, billing frequency, and optional discount. #### listLineItems ```typescript const { results, totalRecords } = await TurboQuote.listLineItems('quote-uuid', { limit: 50, billingFrequency: 'monthly', }); ``` #### addLineItems Add one or more product line items. Pass a single object or an array. ```typescript // Single item await TurboQuote.addLineItems('quote-uuid', { productId: 'product-uuid', // null for a custom (freeform) line item productName: 'Professional Services', unitPrice: 150, billingFrequency: 'one-time', quantity: 8, discountPercent: 10, discountType: 'percent', }); // Multiple items at once await TurboQuote.addLineItems('quote-uuid', [ { productId: 'p1', productName: 'Licence A', unitPrice: 500, billingFrequency: 'annual' }, { productId: 'p2', productName: 'Licence B', unitPrice: 300, billingFrequency: 'annual' }, ]); ``` #### addBundleLineItems Add one or more bundle line items. ```typescript await TurboQuote.addBundleLineItems('quote-uuid', { bundleId: 'bundle-uuid', bundleName: 'Starter Bundle', quantity: 2, showItemsToEndUser: true, }); ``` #### updateLineItem Update a single line item's price, quantity, discount, or billing frequency. ```typescript const updated = await TurboQuote.updateLineItem('quote-uuid', 'item-uuid', { quantity: 12, discountPercent: 15, billingFrequency: 'monthly', }); ``` #### removeLineItem Remove a line item from a quote. ```typescript const { message } = await TurboQuote.removeLineItem('quote-uuid', 'item-uuid'); ``` --- ### Products Manage your product catalog. Products can include images (uploaded as multipart form data — the SDK detects the MIME type from magic bytes automatically). | Method | Signature | Returns | |---|---|---| | `listProducts` | `(opts?) → ProductListResponse` | Paginated list + catalog stats | | `createProduct` | `(req) → Product` | Created product | | `getProduct` | `(id) → Product` | Single product | | `updateProduct` | `(id, req) → Product` | Updated product | | `deleteProduct` | `(id) → SuccessResponse` | Message | | `duplicateProduct` | `(id) → Product` | New duplicate | | `getProductPrimaryImages` | `(productIds[]) → { [id]: ProductImage \| null }` | Primary image map | ```typescript // Create a product with an image const product = await TurboQuote.createProduct({ name: 'Enterprise Licence', listPrice: 1200, billingFrequency: 'annual', categoryId: 'category-uuid', sku: 'ENT-001', showInCatalog: true, images: ['/path/to/product-image.png'], // file path, Buffer, or File object }); // List with filters const { results, totalProducts } = await TurboQuote.listProducts({ categoryIds: ['cat-1', 'cat-2'], billingFrequency: 'monthly', showInCatalog: true, }); // Fetch primary images for multiple products at once const images = await TurboQuote.getProductPrimaryImages(['p-uuid-1', 'p-uuid-2']); // images['p-uuid-1'] → ProductImage | null ``` --- ### Bundles Bundles group multiple products into a single purchasable unit with optional bundle-level discounts. | Method | Signature | Returns | |---|---|---| | `listBundles` | `(opts?) → BundleListResponse` | Paginated list + stats | | `createBundle` | `(req) → Bundle` | Created bundle | | `getBundle` | `(id) → Bundle` | Single bundle | | `updateBundle` | `(id, req) → Bundle` | Updated bundle | | `deleteBundle` | `(id) → SuccessResponse` | Message | | `duplicateBundle` | `(id) → Bundle` | New duplicate | ```typescript const bundle = await TurboQuote.createBundle({ name: 'Starter Pack', categoryId: 'category-uuid', currency: 'USD', showInCatalog: true, syncWithProducts: true, items: [ { productId: 'p1', unitPrice: 200, billingFrequency: 'monthly', quantity: 1 }, { productId: 'p2', unitPrice: 50, billingFrequency: 'monthly', quantity: 3 }, ], }); ``` --- ### Price Books Price books let you define alternative pricing tiers. When a price book is applied to a quote, matching product line items are repriced automatically. | Method | Signature | Returns | |---|---|---| | `listPriceBooks` | `(opts?) → PriceBookListResponse` | Paginated list + stats | | `createPriceBook` | `(req) → PriceBook` | Created price book | | `getPriceBook` | `(id) → PriceBook` | Single price book | | `updatePriceBook` | `(id, req) → PriceBook` | Updated price book | | `deletePriceBook` | `(id) → SuccessResponse` | Message | | `duplicatePriceBook` | `(id) → PriceBook` | New duplicate | | `listPriceBookProducts` | `(id, opts?) → PaginatedResponse` | Per-product pricing | ```typescript const pb = await TurboQuote.createPriceBook({ name: 'Partner Tier', priceBookTypeId: 'type-uuid', validFrom: '2026-01-01', validTo: '2026-12-31', discountPercent: 20, isDefault: false, showInQuoteBuilder: true, productPricing: [ { productId: 'p1', discountPercent: 25, finalPrice: 900 }, ], }); // Apply to a quote const { updatedCount, skippedCount } = await TurboQuote.applyPriceBook( 'quote-uuid', pb.id, ); ``` --- ### Companies Companies represent the buyer organisations your quotes are addressed to. Each company must have at least one contact. | Method | Signature | Returns | |---|---|---| | `listCompanies` | `(opts?) → CompanyListResponse` | Paginated list | | `createCompany` | `(req) → Company` | Created company | | `getCompany` | `(id) → Company` | Single company | | `updateCompany` | `(id, req) → Company` | Updated company | | `deleteCompany` | `(id) → SuccessResponse` | Message | | `listCompanyContacts` | `(companyId, opts?) → ContactListResponse` | Company's contacts | ```typescript const company = await TurboQuote.createCompany({ name: 'Acme Corporation', phone: '+1-555-0100', city: 'San Francisco', state: 'CA', country: 'US', contacts: [ { name: 'Alice Smith', email: 'alice@acme.example', title: 'VP of Engineering' }, ], }); const { results: contacts } = await TurboQuote.listCompanyContacts(company.id); ``` --- ### Contacts Contacts belong to a company and are the individuals a quote is addressed to. :::note No getContact endpoint There is no `getContact(id)` method — the backend does not expose a `GET /v1/contacts/:id` route. Retrieve an individual contact via `listContacts` with a search query, or fetch all contacts for a company with `listCompanyContacts`. ::: | Method | Signature | Returns | |---|---|---| | `listContacts` | `(opts?) → ContactListResponse` | Paginated list | | `createContact` | `(req) → Contact` | Created contact | | `updateContact` | `(id, req) → Contact` | Updated contact | | `deleteContact` | `(id) → SuccessResponse` | Message | ```typescript const contact = await TurboQuote.createContact({ name: 'Bob Jones', companyId: 'company-uuid', email: 'bob@acme.example', title: 'Procurement Manager', }); const { results } = await TurboQuote.listContacts({ companyId: 'company-uuid' }); ``` --- ### Quote Templates Quote templates control the visual presentation of the sent quote (logo, brand colours, disclaimer, terms, sender info). There is one active template per org, accessible via `getTemplate()`. You can also manage named templates. :::note Singleton vs. list `getTemplate()` returns the org's single active template via `GET /v1/quote-template` (singular path). `listTemplates()` returns all named templates via `GET /v1/quote-templates` (plural path). ::: | Method | Signature | Returns | |---|---|---| | `getTemplate` | `() → QuoteTemplate` | Active org template | | `listTemplates` | `(opts?) → QuoteTemplateListResponse` | All named templates | | `getTemplateById` | `(id) → QuoteTemplate` | Named template by ID | | `createTemplate` | `(req) → QuoteTemplate` | Created template | | `updateTemplate` | `(id, req) → QuoteTemplate` | Updated template | | `deleteTemplate` | `(id) → SuccessResponse` | Message | ```typescript // Fetch the active template const tmpl = await TurboQuote.getTemplate(); console.log(tmpl.primaryColor); // e.g. "#1a73e8" // Create a named template const branded = await TurboQuote.createTemplate({ logoUrl: 'https://cdn.example.com/logo.png', primaryColor: '#0057b8', primaryTextColor: '#ffffff', disclaimer: 'Prices valid for 30 days.', termsAndConditions: 'See attached terms...', senderName: 'TurboDocx Sales', senderEmail: 'sales@example.com', }); ``` --- ### Types (Categories) Types are reusable category/classification values used across products, bundles, price books, and companies (e.g., industry tags, price book types). :::note No getType endpoint There is no `getType(id)` method — the backend does not expose `GET /v1/types/:id`. Use `listTypes` to retrieve individual types. ::: | Method | Signature | Returns | |---|---|---| | `listTypes` | `(opts?) → QuoteTypeListResponse` | Paginated list | | `createType` | `(req) → QuoteType` | Created type | | `updateType` | `(id, req) → QuoteType` | Updated type | | `deleteType` | `(id) → SuccessResponse` | Message | ```typescript const type = await TurboQuote.createType({ name: 'Software', categoryType: 'product_category', // 'product_category'|'pricebook_type'|'company_industry'|'bundle_category' }); const { results } = await TurboQuote.listTypes({ categoryType: 'company_industry', includeUsage: true, }); ``` --- ## TypeScript Types Key types exported from `@turbodocx/sdk`: ```typescript // Core quote Quote, QuoteStatusInfo, CreateQuoteRequest, UpdateQuoteRequest, ListQuotesOptions, QuoteListResponse, SendQuoteRequest, SendQuoteWithDeliverableRequest, DeclineQuoteRequest, VoidQuoteRequest, HandleExpiredQuoteRequest, ApplyPriceBookResponse, CreateAndSendRequest, // Line items LineItem, AddLineItemRequest, AddBundleLineItemRequest, UpdateLineItemRequest, ListLineItemsOptions, // Products Product, CreateProductRequest, UpdateProductRequest, ListProductsOptions, // Bundles Bundle, CreateBundleRequest, UpdateBundleRequest, // Price books PriceBook, CreatePriceBookRequest, // Companies & contacts Company, CreateCompanyRequest, Contact, CreateContactRequest, // Templates & types QuoteTemplate, QuoteType, // Enums QuoteStatus, BillingFrequency, Currency, RenewalPeriod, LineItemType, DiscountType, CategoryType, // Shared SuccessResponse, } from '@turbodocx/sdk'; ``` ### Key Enum Values | Type | Values | |---|---| | `QuoteStatus` | `'draft'` `'pending_approval'` `'sent'` `'accepted'` `'declined'` `'voided'` | | `BillingFrequency` | `'monthly'` `'quarterly'` `'annual'` `'one-time'` | | `Currency` | `'USD'` `'EUR'` `'GBP'` `'CAD'` `'AUD'` `'INR'` | | `RenewalPeriod` | `'weekly'` `'monthly'` `'quarterly'` `'annually'` | | `DiscountType` | `'percent'` `'amount'` | | `CategoryType` | `'product_category'` `'pricebook_type'` `'company_industry'` `'bundle_category'` | --- ## Error Handling ```typescript TurboQuote, TurboDocxError, AuthenticationError, AuthorizationError, ValidationError, NotFoundError, RateLimitError, NetworkError, } from '@turbodocx/sdk'; try { await TurboQuote.sendQuote('quote-uuid'); } catch (e) { if (e instanceof ValidationError) { // 400 — e.g. quote is not in draft status, missing required fields console.error('Validation failed:', e.message); } else if (e instanceof NotFoundError) { // 404 — quote, company, contact, or product does not exist console.error('Not found:', e.message); } else if (e instanceof AuthenticationError) { // 401 — bad or revoked API key console.error('Auth failed:', e.message); } else if (e instanceof AuthorizationError) { // 403 — API key does not have permission for this org console.error('Forbidden:', e.message); } else if (e instanceof RateLimitError) { // 429 — back off and retry console.error('Rate limited — retry after a moment'); } else if (e instanceof NetworkError) { // request never reached the server console.error('Network error:', e.message); } else if (e instanceof TurboDocxError) { // catch-all for any other typed SDK error console.error(`Error ${e.statusCode}: ${e.message}`); } else { throw e; } } ``` ### Common Error Codes | Status | Class | When | |---|---|---| | 400 | `ValidationError` | Invalid request body, wrong quote status for transition | | 401 | `AuthenticationError` | Missing or invalid API key | | 403 | `AuthorizationError` | Valid key without permission for this resource | | 404 | `NotFoundError` | Quote, company, product, or contact not found | | 429 | `RateLimitError` | Rate limit exceeded — back off and retry | --- ## Runnable Examples Validated end-to-end examples live in the SDK repo: - **[`turboquote-basic.ts`](https://github.com/TurboDocx/SDK/blob/main/packages/js-sdk/examples/turboquote-basic.ts)** — full quote lifecycle (create → add line items → send → download PDF → delete) - **[`turboquote-products.ts`](https://github.com/TurboDocx/SDK/blob/main/packages/js-sdk/examples/turboquote-products.ts)** — product and bundle catalog management - **[`turboquote-pricebooks.ts`](https://github.com/TurboDocx/SDK/blob/main/packages/js-sdk/examples/turboquote-pricebooks.ts)** — price book CRUD and `applyPriceBook` Run any example with: ```bash export TURBODOCX_API_KEY=your_key export TURBODOCX_ORG_ID=your_org_id npx tsx examples/turboquote-basic.ts ``` --- ## See Also - [TurboSign JavaScript SDK](/docs/SDKs/javascript) — send documents for e-signature - [TurboWebhooks JavaScript SDK](/docs/SDKs/webhooks-javascript) — receive real-time signature events - [Deliverable JavaScript SDK](/docs/SDKs/deliverable-javascript) — generate documents from templates - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [@turbodocx/sdk on npm](https://www.npmjs.com/package/@turbodocx/sdk) - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) --- # TurboQuote PHP SDK The official TurboDocx TurboQuote SDK for PHP applications. Build full CPQ (configure, price, quote) workflows: create quotes, add product and bundle line items, apply price books, send proposals to contacts, and download PDF exports — all from PHP 8.1+. Available on Packagist as `turbodocx/sdk` (same package as TurboSign, TurboWebhooks, and Deliverable). :::info What is TurboQuote? TurboQuote is TurboDocx's quoting and proposal engine. It covers the full quote lifecycle — draft, send, accept/decline/void — with a product catalog, bundle groupings, price books, company/contact CRM, and customizable quote templates. Quotes can be sent with an attached Deliverable document for a branded proposal experience. ::: ## Installation ```bash composer require turbodocx/sdk ``` ## Requirements - PHP 8.1 or higher - Composer 2.x - ext-json - A TurboDocx API key (`TDX-` prefix) — generate one in **Settings → API Keys** ## Configuration ```php ```php :::tip No senderEmail required Unlike TurboSign, `TurboQuote` does **not** require `senderEmail` or `senderName`. Quotes are not signature emails — only `apiKey` and, optionally, `orgId` are needed. ::: ### Environment Variables ```bash TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com ``` :::caution API Credentials Required Both `apiKey` and `orgId` are needed for most API requests. To get your credentials, follow the **[Get Your Credentials](/docs/SDKs#1-get-your-credentials)** steps from the SDKs main page. ::: --- ## Quick Start ### Full quote lifecycle: create → add items → send → download PDF ```php id}\n"; // 2. Add a product line item $items = TurboQuote::addLineItems($quote->id, new AddLineItemRequest( productId: 'product-uuid', productName: 'Enterprise Seat', unitPrice: 199.00, billingFrequency: 'monthly', quantity: 5, )); echo "Added {count($items)} item(s)\n"; // 3. Send the quote $sent = TurboQuote::sendQuote($quote->id); echo "Status: {$sent->quote->status}\n"; // 'sent' // 4. Download the PDF $pdfBytes = TurboQuote::downloadQuotePdf($quote->id); file_put_contents('proposal.pdf', $pdfBytes); echo "PDF saved.\n"; ``` :::caution Always Handle Errors The above example omits error handling for brevity. In production, wrap all TurboQuote calls in try-catch blocks. See [Error Handling](#error-handling) for complete patterns. ::: ### Convenience: createAndSend ```php quote->id}\n"; ``` --- ## Method Reference All methods are static. Configure once with `TurboQuote::configure(...)`, then call on the class directly. ### Quotes #### listQuotes ```php use TurboDocx\Types\Requests\Quote\ListQuotesRequest; $page = TurboQuote::listQuotes(new ListQuotesRequest( limit: 10, offset: 0, query: 'Enterprise', )); echo "Total: {$page->totalRecords}\n"; foreach ($page->results as $q) { echo " [{$q->status}] {$q->name}\n"; } ``` #### createQuote ```php use TurboDocx\Types\Requests\Quote\CreateQuoteRequest; $quote = TurboQuote::createQuote(new CreateQuoteRequest( name: 'Q3 Proposal', companyId: 'company-uuid', contactId: 'contact-uuid', validUntil: '2026-09-30', currency: 'USD', )); ``` #### getQuote ```php $quote = TurboQuote::getQuote('quote-uuid'); // statusInfo is merged onto the returned Quote object when present echo $quote->status; ``` #### updateQuote ```php use TurboDocx\Types\Requests\Quote\UpdateQuoteRequest; $quote = TurboQuote::updateQuote('quote-uuid', new UpdateQuoteRequest( name: 'Q3 Proposal — Revised', validUntil: '2026-10-15', )); ``` #### deleteQuote ```php $result = TurboQuote::deleteQuote('quote-uuid'); echo $result->message; ``` #### duplicateQuote ```php $copy = TurboQuote::duplicateQuote('quote-uuid'); echo "Copy id: {$copy->id}"; ``` #### downloadQuotePdf Returns raw PDF bytes. Save to disk or stream to the browser. ```php $pdfBytes = TurboQuote::downloadQuotePdf('quote-uuid'); file_put_contents('quote.pdf', $pdfBytes); // Or stream as HTTP response header('Content-Type: application/pdf'); header('Content-Disposition: attachment; filename="quote.pdf"'); echo $pdfBytes; ``` ### Quote Status Transitions #### sendQuote ```php use TurboDocx\Types\Requests\Quote\SendQuoteRequest; $result = TurboQuote::sendQuote('quote-uuid', new SendQuoteRequest( // optional overrides; pass null to use quote defaults )); // $result->quote — updated Quote // $result->message ``` #### sendQuoteWithDeliverable Attach a Deliverable document (generated DOCX/PDF) alongside the quote. ```php use TurboDocx\Types\Requests\Quote\SendQuoteWithDeliverableRequest; $result = TurboQuote::sendQuoteWithDeliverable('quote-uuid', new SendQuoteWithDeliverableRequest( deliverableId: 'deliverable-uuid', mergePosition: 'end', )); // $result->quote // $result->message // $result->documentId — TurboSign document ID ``` #### declineQuote ```php use TurboDocx\Types\Requests\Quote\DeclineQuoteRequest; $quote = TurboQuote::declineQuote('quote-uuid', new DeclineQuoteRequest( reason: 'Price out of budget', )); ``` #### voidQuote ```php use TurboDocx\Types\Requests\Quote\VoidQuoteRequest; $quote = TurboQuote::voidQuote('quote-uuid', new VoidQuoteRequest( reason: 'Superseded by new proposal', )); ``` #### handleExpiredQuote ```php use TurboDocx\Types\Requests\Quote\HandleExpiredQuoteRequest; $quote = TurboQuote::handleExpiredQuote('quote-uuid', new HandleExpiredQuoteRequest( action: 'extend', reason: 'Customer requested more time to review', newValidUntil: '2026-12-31', )); ``` ### Price Books on Quotes #### applyPriceBook Apply a price book to a quote, updating matching line item prices. ```php $result = TurboQuote::applyPriceBook('quote-uuid', 'pricebook-uuid'); // $result->quote // $result->message // $result->updatedCount — number of items re-priced // $result->skippedCount — items with no matching pricebook entry ``` #### removePriceBook ```php $quote = TurboQuote::removePriceBook('quote-uuid'); ``` ### Line Items #### listLineItems ```php use TurboDocx\Types\Requests\Quote\ListLineItemsRequest; $page = TurboQuote::listLineItems('quote-uuid', new ListLineItemsRequest( limit: 50, )); foreach ($page->results as $item) { echo " {$item->productName}: {$item->unitPrice} x {$item->quantity}\n"; } ``` #### addLineItems Pass a single `AddLineItemRequest` or an array of them. ```php use TurboDocx\Types\Requests\Quote\AddLineItemRequest; $items = TurboQuote::addLineItems('quote-uuid', [ new AddLineItemRequest( productId: 'product-uuid-1', productName: 'Platform Subscription', unitPrice: 199.00, billingFrequency: 'monthly', quantity: 2, ), new AddLineItemRequest( productId: 'product-uuid-2', productName: 'Onboarding', unitPrice: 499.00, billingFrequency: 'one-time', quantity: 1, ), ]); ``` #### addBundleLineItems Pass a single `AddBundleLineItemRequest` or an array of them. ```php use TurboDocx\Types\Requests\Quote\AddBundleLineItemRequest; $items = TurboQuote::addBundleLineItems('quote-uuid', [ new AddBundleLineItemRequest( bundleId: 'bundle-uuid-1', bundleName: 'Starter Bundle', quantity: 1, ), new AddBundleLineItemRequest( bundleId: 'bundle-uuid-2', bundleName: 'Premium Add-ons', quantity: 2, ), ]); ``` #### updateLineItem ```php use TurboDocx\Types\Requests\Quote\UpdateLineItemRequest; $item = TurboQuote::updateLineItem('quote-uuid', 'item-uuid', new UpdateLineItemRequest( quantity: 3, unitPrice: 189.00, )); ``` #### removeLineItem ```php $result = TurboQuote::removeLineItem('quote-uuid', 'item-uuid'); echo $result->message; ``` ### Products | Method | Signature | Returns | |---|---|---| | `listProducts` | `(?ListProductsRequest)` | `ProductListResponse` | | `createProduct` | `(CreateProductRequest)` | `Product` | | `getProduct` | `(string $id)` | `Product` | | `updateProduct` | `(string $id, UpdateProductRequest)` | `Product` | | `deleteProduct` | `(string $id)` | `MessageResponse` | | `duplicateProduct` | `(string $id)` | `Product` | | `getProductPrimaryImages` | `(string[] $productIds)` | `array` | ```php use TurboDocx\Types\Requests\Quote\CreateProductRequest; use TurboDocx\Types\Requests\Quote\ListProductsRequest; // List products $page = TurboQuote::listProducts(new ListProductsRequest(limit: 20, query: 'license')); // Create a product $product = TurboQuote::createProduct(new CreateProductRequest( name: 'Enterprise Seat', listPrice: 299.00, billingFrequency: 'monthly', categoryId: 'category-uuid', sku: 'ENT-001', )); // Fetch primary images for a set of product IDs $images = TurboQuote::getProductPrimaryImages(['product-uuid-1', 'product-uuid-2']); // $images['product-uuid-1'] => image array or null ``` :::note Product images When `CreateProductRequest` or `UpdateProductRequest` includes an `images` key (array of file paths or raw bytes), the SDK automatically switches to multipart form upload. The MIME type is detected from magic bytes on the server side. ::: ### Bundles | Method | Signature | Returns | |---|---|---| | `listBundles` | `(?ListBundlesRequest)` | `BundleListResponse` | | `createBundle` | `(CreateBundleRequest)` | `Bundle` | | `getBundle` | `(string $id)` | `Bundle` | | `updateBundle` | `(string $id, UpdateBundleRequest)` | `Bundle` | | `deleteBundle` | `(string $id)` | `MessageResponse` | | `duplicateBundle` | `(string $id)` | `Bundle` | ```php use TurboDocx\Types\Requests\Quote\CreateBundleRequest; $bundle = TurboQuote::createBundle(new CreateBundleRequest( name: 'Starter Kit', categoryId: 'category-uuid', items: [ ['productId' => 'product-uuid-1', 'quantity' => 1], ['productId' => 'product-uuid-2', 'quantity' => 2], ], )); $copy = TurboQuote::duplicateBundle($bundle->id); ``` ### Price Books | Method | Signature | Returns | |---|---|---| | `listPriceBooks` | `(?ListPriceBooksRequest)` | `PriceBookListResponse` | | `createPriceBook` | `(CreatePriceBookRequest)` | `PriceBook` | | `getPriceBook` | `(string $id)` | `PriceBook` | | `updatePriceBook` | `(string $id, UpdatePriceBookRequest)` | `PriceBook` | | `deletePriceBook` | `(string $id)` | `MessageResponse` | | `duplicatePriceBook` | `(string $id)` | `PriceBook` | | `listPriceBookProducts` | `(string $id, ?ListPriceBookProductsRequest)` | `PriceBookProductListResponse` | ```php use TurboDocx\Types\Requests\Quote\CreatePriceBookRequest; $pb = TurboQuote::createPriceBook(new CreatePriceBookRequest( name: 'Partner Discount', priceBookTypeId: 'pricebook-type-uuid', validFrom: '2026-01-01', discountPercent: 15.0, )); // List products enrolled in the price book $products = TurboQuote::listPriceBookProducts($pb->id); ``` ### Companies | Method | Signature | Returns | |---|---|---| | `listCompanies` | `(?ListCompaniesRequest)` | `CompanyListResponse` | | `createCompany` | `(CreateCompanyRequest)` | `Company` | | `getCompany` | `(string $id)` | `Company` | | `updateCompany` | `(string $id, UpdateCompanyRequest)` | `Company` | | `deleteCompany` | `(string $id)` | `MessageResponse` | | `listCompanyContacts` | `(string $companyId, ?ListContactsRequest)` | `ContactListResponse` | ```php use TurboDocx\Types\Requests\Quote\CreateCompanyRequest; // A company requires at least one contact on creation $company = TurboQuote::createCompany(new CreateCompanyRequest( name: 'Acme Corp', contacts: [ ['name' => 'Jane Doe', 'email' => 'jane@acme.com'], ], )); $contacts = TurboQuote::listCompanyContacts($company->id); ``` ### Contacts | Method | Signature | Returns | |---|---|---| | `listContacts` | `(?ListContactsRequest)` | `ContactListResponse` | | `createContact` | `(CreateContactRequest)` | `Contact` | | `updateContact` | `(string $id, UpdateContactRequest)` | `Contact` | | `deleteContact` | `(string $id)` | `MessageResponse` | :::note No getContact There is no `getContact($id)` — the backend does not expose a `GET /v1/contacts/:id` endpoint. Use `listContacts` with a search query or `listCompanyContacts` instead. ::: ```php use TurboDocx\Types\Requests\Quote\CreateContactRequest; $contact = TurboQuote::createContact(new CreateContactRequest( name: 'John Smith', companyId: 'company-uuid', email: 'john@acme.com', )); ``` ### Quote Templates | Method | Signature | Returns | |---|---|---| | `listTemplates` | `(?ListTemplatesRequest)` | `QuoteTemplateListResponse` | | `getTemplate` | `()` | `QuoteTemplate` | | `getTemplateById` | `(string $id)` | `QuoteTemplate` | | `createTemplate` | `(CreateQuoteTemplateRequest)` | `QuoteTemplate` | | `updateTemplate` | `(string $id, UpdateQuoteTemplateRequest)` | `QuoteTemplate` | | `deleteTemplate` | `(string $id)` | `MessageResponse` | :::note getTemplate vs getTemplateById `getTemplate()` (no argument) hits `GET /v1/quote-template` (singular) and returns the org's currently active template. `getTemplateById($id)` hits `GET /v1/quote-templates/:id` and returns any specific template by ID. ::: ```php // Get the active org template $active = TurboQuote::getTemplate(); echo $active->name; // List all templates $templates = TurboQuote::listTemplates(); ``` ### Types / Categories | Method | Signature | Returns | |---|---|---| | `listTypes` | `(?ListTypesRequest)` | `QuoteTypeListResponse` | | `createType` | `(CreateQuoteTypeRequest)` | `QuoteType` | | `updateType` | `(string $id, UpdateQuoteTypeRequest)` | `QuoteType` | | `deleteType` | `(string $id)` | `MessageResponse` | :::note No getType There is no `getType($id)` — the backend does not expose a `GET /v1/types/:id` endpoint. Use `listTypes` instead. ::: ```php use TurboDocx\Types\Requests\Quote\CreateQuoteTypeRequest; $type = TurboQuote::createType(new CreateQuoteTypeRequest(name: 'Renewal')); ``` ### Convenience Method #### createAndSend Creates a quote, optionally adds line items and bundle items, then sends it — all in a single call. ```php use TurboDocx\Types\Requests\Quote\CreateAndSendRequest; use TurboDocx\Types\Requests\Quote\AddLineItemRequest; use TurboDocx\Types\Requests\Quote\AddBundleLineItemRequest; use TurboDocx\Types\Requests\Quote\SendQuoteRequest; $result = TurboQuote::createAndSend(new CreateAndSendRequest( name: 'Annual Plan', companyId: 'company-uuid', contactId: 'contact-uuid', validUntil: '2026-12-31', items: [ new AddLineItemRequest( productId: 'product-uuid', productName: 'Annual License', unitPrice: 999.00, billingFrequency: 'annual', quantity: 10, ), ], bundleItems: [ new AddBundleLineItemRequest( bundleId: 'bundle-uuid', bundleName: 'Annual Bundle', quantity: 1, ), ], send: new SendQuoteRequest(), )); echo "Sent quote: {$result->quote->id}\n"; ``` --- ## Error Handling ```php use TurboDocx\Exceptions\TurboDocxException; use TurboDocx\Exceptions\AuthenticationException; use TurboDocx\Exceptions\AuthorizationException; use TurboDocx\Exceptions\ValidationException; use TurboDocx\Exceptions\NotFoundException; use TurboDocx\Exceptions\RateLimitException; use TurboDocx\Exceptions\NetworkException; try { $quote = TurboQuote::createQuote(new CreateQuoteRequest( name: 'Q3 Proposal', companyId: 'company-uuid', contactId: 'contact-uuid', )); } catch (ValidationException $e) { // 400 — missing required fields, invalid values echo "Validation error: {$e->getMessage()}\n"; } catch (AuthenticationException $e) { // 401 — missing or revoked API key echo "Auth failed: {$e->getMessage()}\n"; } catch (AuthorizationException $e) { // 403 — key exists but lacks permission echo "Forbidden: {$e->getMessage()}\n"; } catch (NotFoundException $e) { // 404 — quote, company, product, etc. not found echo "Not found: {$e->getMessage()}\n"; } catch (RateLimitException $e) { // 429 — back off and retry echo "Rate limited: {$e->getMessage()}\n"; } catch (NetworkException $e) { // request never reached the server echo "Network error: {$e->getMessage()}\n"; } catch (TurboDocxException $e) { // catch-all for any other typed SDK error echo "Error {$e->statusCode}: {$e->getMessage()}\n"; } ``` ### Common Error Codes | Status | Exception | When | |---|---|---| | 400 | `ValidationException` | Missing required fields, invalid enum value, malformed body | | 401 | `AuthenticationException` | Missing or invalid API key | | 403 | `AuthorizationException` | Valid key without sufficient permissions | | 404 | `NotFoundException` | Quote, product, company, etc. does not exist | | 409 | `TurboDocxException` | Conflict (e.g., duplicate SKU) | | 429 | `RateLimitException` | Rate limit exceeded — back off | --- ## See Also - [TurboQuote Python SDK](/docs/SDKs/quote-python) — same surface in Python - [TurboSign PHP SDK](/docs/SDKs/php) — sending documents for signature from PHP - [TurboWebhooks PHP SDK](/docs/SDKs/webhooks-php) — receiving signature events in PHP - [Deliverable PHP SDK](/docs/SDKs/deliverable-php) — document generation from PHP - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [TurboDocx SDK on Packagist](https://packagist.org/packages/turbodocx/sdk) - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) --- # TurboQuote Python SDK The official TurboDocx TurboQuote SDK for Python applications. Build full CPQ (configure, price, quote) workflows: create quotes, add product and bundle line items, apply price books, send proposals to contacts, and download PDF exports — all from async Python 3.9+. Distributed on PyPI as `turbodocx-sdk` (same package as TurboSign, TurboWebhooks, and Deliverable). :::info What is TurboQuote? TurboQuote is TurboDocx's quoting and proposal engine. It covers the full quote lifecycle — draft, send, accept/decline/void — with a product catalog, bundle groupings, price books, company/contact CRM, and customizable quote templates. Quotes can be sent with an attached Deliverable document for a branded proposal experience. ::: ## Installation ```bash pip install turbodocx-sdk ``` ```bash poetry add turbodocx-sdk ``` ```bash pipenv install turbodocx-sdk ``` ## Requirements - Python 3.9 or higher - `httpx` (installed automatically as a dependency) - A TurboDocx API key (`TDX-` prefix) — generate one in **Settings → API Keys** - All SDK methods are `async` — call them from an `async def` (or wrap with `asyncio.run(...)` in synchronous contexts) ## Configuration ```python from turbodocx_sdk import TurboQuote TurboQuote.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], # optional — auto-reads env var ) ``` :::tip No sender email required Unlike TurboSign, `TurboQuote.configure()` does **not** require `sender_email` or `sender_name`. Quotes are not signature emails. Only `api_key` and, optionally, `org_id` are needed. If you skip the explicit call, the SDK lazily auto-configures itself from `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID` on first method invocation. ::: ### Environment Variables ```bash TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com ``` ## Quick Start ### Create a quote, add line items, send, and download the PDF ```python from turbodocx_sdk import TurboQuote TurboQuote.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], ) async def full_quote_lifecycle(): # 1. Create a draft quote quote = await TurboQuote.create_quote({ "name": "Acme Corp — Annual Plan Q3", "companyId": "company-uuid", "contactId": "contact-uuid", "validUntil": "2026-09-30", "currency": "USD", }) quote_id = quote["id"] print(f"Created quote {quote_id}") # 2. Add product line items items = await TurboQuote.add_line_items(quote_id, [ { "productId": "product-uuid-1", "productName": "Platform License", "quantity": 5, "unitPrice": "199.00", "billingFrequency": "annual", }, { "productId": "product-uuid-2", "productName": "Onboarding Package", "quantity": 1, "unitPrice": "499.00", "billingFrequency": "one-time", }, ]) print(f"Added {len(items)} line items") # 3. Apply a price book (optional) result = await TurboQuote.apply_price_book(quote_id, "pricebook-uuid") print(f"Price book applied: {result['updatedCount']} items updated") # 4. Send the quote sent = await TurboQuote.send_quote(quote_id, { "ccEmails": ["manager@acme.com"], "validUntil": "2026-09-30", }) print(f"Sent: {sent['message']}") # 5. Download the PDF pdf_bytes = await TurboQuote.download_quote_pdf(quote_id) with open("acme-quote.pdf", "wb") as f: f.write(pdf_bytes) print(f"PDF saved ({len(pdf_bytes)} bytes)") asyncio.run(full_quote_lifecycle()) ``` ### Convenience: create, add items, and send in one call ```python result = await TurboQuote.create_and_send({ "name": "Acme Corp — Quick Proposal", "companyId": "company-uuid", "contactId": "contact-uuid", "currency": "USD", "items": [ {"productId": "product-uuid-1", "productName": "Platform License", "quantity": 3, "unitPrice": "299.00", "billingFrequency": "monthly"}, ], "bundleItems": [ {"bundleId": "bundle-uuid-1", "bundleName": "Starter Pack", "quantity": 1}, ], "send": { "ccEmails": ["cc@example.com"], "validUntil": "2026-09-30", }, }) print(f"Quote sent: {result['quote']['id']}") ``` --- ## Method Reference All methods are `@classmethod`s on `TurboQuote`; configure once, then call on the class. ### Quotes #### `list_quotes` ```python page = await TurboQuote.list_quotes({ "limit": 20, "offset": 0, "query": "acme", "statuses": ["draft", "sent"], # repeated-key filter }) # page["results"], page["totalRecords"], page["stats"] ``` #### `create_quote` ```python quote = await TurboQuote.create_quote({ "name": "Q3 Proposal", "companyId": "company-uuid", # required "contactId": "contact-uuid", # required "currency": "USD", "validUntil": "2026-09-30", "notes": "Includes implementation services", "taxRate": "8.5", }) # returns Quote dict ``` #### `get_quote` Returns the quote with `statusInfo` merged in when the quote is in a terminal state. ```python quote = await TurboQuote.get_quote("quote-uuid") # quote["status"], quote["grandTotal"], quote.get("statusInfo") ``` #### `update_quote` All fields are optional — pass only what changes. Send an explicit `None` to clear a nullable field. ```python updated = await TurboQuote.update_quote("quote-uuid", { "name": "Q3 Proposal — Revised", "validUntil": "2026-10-15", "taxRate": None, # clears the field }) ``` #### `delete_quote` ```python result = await TurboQuote.delete_quote("quote-uuid") # result["message"] ``` #### `duplicate_quote` ```python new_quote = await TurboQuote.duplicate_quote("quote-uuid") # returns new Quote in draft status ``` #### `download_quote_pdf` Returns raw PDF bytes. ```python pdf_bytes = await TurboQuote.download_quote_pdf("quote-uuid") with open("quote.pdf", "wb") as f: f.write(pdf_bytes) ``` --- ### Quote Status Transitions #### `send_quote` ```python sent = await TurboQuote.send_quote("quote-uuid", { "ccEmails": ["manager@example.com"], "validUntil": "2026-09-30", }) # sent["quote"], sent["message"] ``` #### `send_quote_with_deliverable` Attach a TurboDocx-generated document to the sent quote. ```python result = await TurboQuote.send_quote_with_deliverable("quote-uuid", { "deliverableId": "deliverable-uuid", "ccEmails": ["manager@example.com"], }) # result["quote"], result["message"], result["documentId"] ``` #### `decline_quote` ```python quote = await TurboQuote.decline_quote("quote-uuid", { "reason": "Customer selected a competitor", }) ``` #### `void_quote` ```python quote = await TurboQuote.void_quote("quote-uuid", { "reason": "Terms expired before acceptance", }) ``` #### `handle_expired_quote` ```python quote = await TurboQuote.handle_expired_quote("quote-uuid", { "action": "void", # "void" or "decline" "reason": "Quote expired", "newValidUntil": "2026-12-31", # optional — extend before re-sending }) ``` --- ### Price Books (on a Quote) #### `apply_price_book` Apply a price book to an existing quote; updates matching line item prices. ```python result = await TurboQuote.apply_price_book("quote-uuid", "pricebook-uuid") # result["quote"] — updated Quote # result["updatedCount"] — int, items that were re-priced # result["skippedCount"] — int, items that had no pricebook match # result["message"] ``` #### `remove_price_book` ```python quote = await TurboQuote.remove_price_book("quote-uuid") ``` --- ### Line Items #### `list_line_items` ```python page = await TurboQuote.list_line_items("quote-uuid", {"limit": 50}) # page["results"], page["totalRecords"] ``` #### `add_line_items` Accepts a single item dict or a list. Returns a list of created `LineItem` dicts. ```python items = await TurboQuote.add_line_items("quote-uuid", [ { "productId": "product-uuid", "productName": "Platform License", "quantity": 2, "unitPrice": "199.00", "billingFrequency": "annual", "discountPercent": "10.0", } ]) ``` #### `add_bundle_line_items` ```python items = await TurboQuote.add_bundle_line_items("quote-uuid", [ { "bundleId": "bundle-uuid", "quantity": 1, } ]) ``` #### `update_line_item` ```python item = await TurboQuote.update_line_item("quote-uuid", "item-uuid", { "quantity": 5, "unitPrice": "179.00", }) ``` #### `remove_line_item` ```python result = await TurboQuote.remove_line_item("quote-uuid", "item-uuid") # result["message"] ``` --- ### Products | Method | Signature | Returns | |---|---|---| | `list_products` | `(options?)` | paginated list | | `create_product` | `(request)` | `Product` | | `get_product` | `(id)` | `Product` | | `update_product` | `(id, request)` | `Product` | | `delete_product` | `(id)` | `{"message": ...}` | | `duplicate_product` | `(id)` | `Product` | | `get_product_primary_images` | `(product_ids)` | `{id: image \| None}` | ```python # Create a product with images (multipart upload auto-detected) product = await TurboQuote.create_product({ "name": "Enterprise License", "listPrice": "499.00", "cost": "200.00", "billingFrequency": "annual", "showInCatalog": True, "images": ["/path/to/product-photo.png"], # file path(s) or bytes }) # List products with filters page = await TurboQuote.list_products({ "limit": 20, "query": "enterprise", "showInCatalog": True, }) # Bulk fetch primary images for a set of product IDs images = await TurboQuote.get_product_primary_images(["id-1", "id-2", "id-3"]) # images["id-1"] → ProductImage dict, or None ``` --- ### Bundles | Method | Signature | Returns | |---|---|---| | `list_bundles` | `(options?)` | paginated list | | `create_bundle` | `(request)` | `Bundle` | | `get_bundle` | `(id)` | `Bundle` | | `update_bundle` | `(id, request)` | `Bundle` | | `delete_bundle` | `(id)` | `{"message": ...}` | | `duplicate_bundle` | `(id)` | `Bundle` | ```python bundle = await TurboQuote.create_bundle({ "name": "Starter Pack", "items": [ {"productId": "product-uuid-1", "quantity": 1}, {"productId": "product-uuid-2", "quantity": 2}, ], "discountPercent": "5.0", }) ``` --- ### Price Books | Method | Signature | Returns | |---|---|---| | `list_price_books` | `(options?)` | paginated list | | `create_price_book` | `(request)` | `PriceBook` | | `get_price_book` | `(id)` | `PriceBook` | | `update_price_book` | `(id, request)` | `PriceBook` | | `delete_price_book` | `(id)` | `{"message": ...}` | | `duplicate_price_book` | `(id)` | `PriceBook` | | `list_price_book_products` | `(id, options?)` | paginated list | ```python # name, priceBookTypeId, and validFrom are REQUIRED. # discountPercent is optional and defaults to 0 if not provided. # priceBookTypeId comes from a create_type with categoryType "pricebook_type". pricebook = await TurboQuote.create_price_book({ "name": "Partner Tier A", "priceBookTypeId": "pricebook-type-uuid", "validFrom": "2026-01-01", "discountPercent": 15, "isDefault": False, "productPricing": [ {"productId": "product-uuid-1", "discountType": "percent", "discountPercent": 25}, {"productId": "product-uuid-2", "discountType": "percent", "discountPercent": 30}, ], }) # List products attached to a price book page = await TurboQuote.list_price_book_products("pricebook-uuid", {"limit": 50}) ``` --- ### Companies | Method | Signature | Returns | |---|---|---| | `list_companies` | `(options?)` | paginated list | | `create_company` | `(request)` | `Company` | | `get_company` | `(id)` | `Company` | | `update_company` | `(id, request)` | `Company` | | `delete_company` | `(id)` | `{"message": ...}` | | `list_company_contacts` | `(company_id, options?)` | paginated list | ```python company = await TurboQuote.create_company({ "name": "Acme Corporation", "domain": "acme.com", "contacts": [ # at least one contact required { "firstName": "Jane", "lastName": "Doe", "email": "jane@acme.com", } ], }) # List contacts for a company contacts = await TurboQuote.list_company_contacts(company["id"]) ``` --- ### Contacts | Method | Signature | Returns | |---|---|---| | `list_contacts` | `(options?)` | paginated list | | `create_contact` | `(request)` | `Contact` | | `update_contact` | `(id, request)` | `Contact` | | `delete_contact` | `(id)` | `{"message": ...}` | :::note No `get_contact` by design The backend has no `GET /v1/contacts/:id` endpoint. Fetch individual contacts via `list_company_contacts` or `list_contacts` with a query filter. ::: ```python contact = await TurboQuote.create_contact({ "firstName": "John", "lastName": "Smith", "email": "john@acme.com", "companyId": "company-uuid", }) ``` --- ### Quote Templates | Method | Signature | Returns | |---|---|---| | `list_templates` | `(options?)` | paginated list | | `get_template` | `()` | singleton `QuoteTemplate` | | `get_template_by_id` | `(id)` | `QuoteTemplate` | | `create_template` | `(request)` | `QuoteTemplate` | | `update_template` | `(id, request)` | `QuoteTemplate` | | `delete_template` | `(id)` | `{"message": ...}` | ```python # Get the org's default quote template (singleton endpoint) default_template = await TurboQuote.get_template() # Get a specific template template = await TurboQuote.get_template_by_id("template-uuid") ``` --- ### Types / Categories | Method | Signature | Returns | |---|---|---| | `list_types` | `(options?)` | paginated list | | `create_type` | `(request)` | `QuoteType` | | `update_type` | `(id, request)` | `QuoteType` | | `delete_type` | `(id)` | `{"message": ...}` | :::note No `get_type` by design The backend has no `GET /v1/types/:id` endpoint. Use `list_types` to retrieve individual type records. ::: ```python quote_type = await TurboQuote.create_type({"name": "New Business"}) await TurboQuote.update_type(quote_type["id"], {"name": "New Logo Business"}) ``` --- ### Convenience #### `create_and_send` Orchestrates multiple API calls: create quote → add product items → add bundle items → send. Returns `{"quote": }`. ```python result = await TurboQuote.create_and_send({ # CreateQuote fields "name": "Year-End Renewal", "companyId": "company-uuid", "contactId": "contact-uuid", "currency": "USD", # optional line items added before send "items": [ {"productId": "product-uuid-1", "productName": "Platform License", "quantity": 10, "unitPrice": "99.00", "billingFrequency": "monthly"}, ], # optional bundle items added before send "bundleItems": [ {"bundleId": "bundle-uuid-1", "bundleName": "Starter Pack", "quantity": 1}, ], # send options (ccEmails, validUntil) "send": { "ccEmails": ["finance@example.com"], "validUntil": "2026-12-31", }, }) print(result["quote"]["id"]) ``` --- ## Error Handling ```python from turbodocx_sdk import ( TurboDocxError, AuthenticationError, AuthorizationError, ValidationError, NotFoundError, ConflictError, RateLimitError, NetworkError, ) try: quote = await TurboQuote.create_quote({ "name": "Q3 Proposal", "companyId": "company-uuid", "contactId": "contact-uuid", }) except ValidationError as e: # 400 — missing required fields, invalid enum value, etc. print(f"Validation failed: {e}") except AuthenticationError: # 401 — bad or revoked API key pass except AuthorizationError: # 403 — key valid but lacks permission for this org/action pass except NotFoundError as e: # 404 — company, contact, or product not found print(f"Not found: {e}") except RateLimitError: # 429 — back off and retry pass except NetworkError: # request never reached the server (DNS, refused, timeout) pass except TurboDocxError as e: # catch-all for any other typed SDK error (raw 5xx, etc.) print(f"Error {getattr(e, 'status_code', '?')}: {e}") ``` ### Error Code Reference | Status | Class | When | |---|---|---| | 400 | `ValidationError` | Invalid fields, missing required keys, bad enum value | | 401 | `AuthenticationError` | Missing or invalid API key | | 403 | `AuthorizationError` | Valid key without permission for this operation | | 404 | `NotFoundError` | Quote, product, company, or other resource not found | | 409 | `ConflictError` | Duplicate resource (e.g., company domain conflict) | | 429 | `RateLimitError` | Rate limit exceeded — back off | --- ## See Also - [TurboQuote JavaScript / TypeScript SDK](/docs/SDKs/quote-javascript) — same API, JS idioms - [TurboSign Python SDK](/docs/SDKs/python) — send documents for e-signature - [TurboWebhooks Python SDK](/docs/SDKs/webhooks-python) — receive real-time signature events - [Deliverable Python SDK](/docs/SDKs/deliverable-python) — generate documents from templates - [SDKs Overview](/docs/SDKs) — all SDKs across all languages - [turbodocx-sdk on PyPI](https://pypi.org/project/turbodocx-sdk/) - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) --- # TurboWebhooks Go SDK The official TurboDocx Webhooks SDK for Go applications (net/http, Gin, Echo, Chi, AWS Lambda, etc.). Subscribe a single per-organization HTTPS endpoint to TurboDocx signature events, verify inbound signatures with HMAC-SHA256, replay delivery attempts, and rotate secrets — all from Go 1.21+. Distributed as `github.com/TurboDocx/SDK/packages/go-sdk` (same module as TurboSign). :::info What is TurboWebhooks? TurboWebhooks lets your application receive real-time notifications when signature documents complete or get voided, instead of polling the API. Each organization has a single, named webhook (`signature`) that mirrors the **Signature Webhooks** page in the dashboard, so SDK-managed and UI-managed configuration stays in sync. For the full conceptual overview of how webhooks work in TurboSign (delivery retries, payload schema, dashboard UI), see [TurboSign → Webhooks](/docs/TurboSign/Webhooks). ::: ## Installation ```bash go get github.com/TurboDocx/SDK/packages/go-sdk ``` Then import: ```go ``` ## Requirements - Go 1.21 or higher - An **administrator** TurboDocx API key (the webhook routes are gated on the administrator role — non-admin keys return HTTP 403) - All client methods accept a `context.Context` — pass `context.Background()` for one-offs or the request context inside handlers ## Configuration ```go "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) wh, err := turbodocx.NewWebhooksClientWithConfig(turbodocx.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } ``` `NewWebhooksClientWithConfig` does **not** require `SenderEmail` — webhook routes don't send email, so the sender validation that `NewClientWithConfig` enforces for TurboSign is skipped here. If `APIKey`, `OrgID`, or `BaseURL` are blank, the SDK falls back to `TURBODOCX_API_KEY`, `TURBODOCX_ORG_ID`, and `TURBODOCX_BASE_URL`. ### Environment Variables ```bash TURBODOCX_API_KEY=your_admin_api_key TURBODOCX_ORG_ID=your_org_id # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com # store the secret returned by CreateWebhook so your receiver can verify signatures TURBODOCX_WEBHOOK_SECRET=whsec_... ``` :::warning Administrator role required TurboWebhooks endpoints require the **administrator** role on the API key. A valid TDX- key without the role returns `*turbodocx.AuthorizationError` (HTTP 403). Generate or rotate keys in the **Settings → API Keys** page. ::: ## Quick Start ### 1. Create the signature webhook ```go package main "context" "errors" "fmt" "log" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { ctx := context.Background() wh, err := turbodocx.NewWebhooksClientWithConfig(turbodocx.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), }) if err != nil { log.Fatal(err) } created, err := wh.CreateWebhook(ctx, turbodocx.CreateWebhookRequest{ URLs: []string{"https://your-server.example.com/webhooks/turbodocx"}, Events: []string{"signature.document.completed", "signature.document.voided"}, }) if err != nil { var conflict *turbodocx.ConflictError var valErr *turbodocx.ValidationError switch { case errors.As(err, &conflict): // 409 — the signature webhook already exists for this org. // Use UpdateWebhook or DeleteWebhook instead. log.Println("Webhook already exists. Use UpdateWebhook or DeleteWebhook.") return case errors.As(err, &valErr): // 400 — most commonly a non-HTTPS URL or empty events array. log.Fatalf("Validation failed: %s", valErr.Message) default: log.Fatal(err) } } // SAVE THIS SECRET — it is shown ONCE and cannot be retrieved later. if err := os.WriteFile(".secret", []byte(created.Secret), 0600); err != nil { log.Fatal(err) } fmt.Printf("Created webhook id=%s\n", created.ID) } ``` :::warning HTTPS only TurboDocx rejects non-HTTPS webhook URLs with HTTP 400. For local development, expose your receiver via an HTTPS tunnel ([ngrok](https://ngrok.com), [cloudflared](https://github.com/cloudflare/cloudflared), or [webhook.site](https://webhook.site)) and pass the tunnel URL to `CreateWebhook`. ::: ### 2. Verify inbound webhook signatures When TurboDocx POSTs to your receiver, every request carries an `X-TurboDocx-Signature` header. Verify it before trusting the payload — the helper enforces a 300-second timestamp tolerance and uses `hmac.Equal` for constant-time comparison. ```go package main "io" "log" "net/http" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func turbodocxWebhook(w http.ResponseWriter, r *http.Request) { // IMPORTANT: read raw bytes — the signature is computed over them. // Decoding to a struct first will lose whitespace and break verification. rawBody, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "read failed", http.StatusBadRequest) return } defer r.Body.Close() signature := r.Header.Get("X-TurboDocx-Signature") timestamp := r.Header.Get("X-TurboDocx-Timestamp") secret := os.Getenv("TURBODOCX_WEBHOOK_SECRET") if !turbodocx.VerifyWebhookSignature(rawBody, signature, timestamp, secret, nil) { http.Error(w, "invalid signature", http.StatusUnauthorized) return } // Now safe to json.Unmarshal(rawBody, &event) and dispatch on event.eventType. w.WriteHeader(http.StatusOK) } func main() { http.HandleFunc("/webhooks/turbodocx", turbodocxWebhook) log.Fatal(http.ListenAndServe(":3000", nil)) } ``` ```go package main "io" "net/http" "os" "github.com/gin-gonic/gin" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { r := gin.Default() r.POST("/webhooks/turbodocx", func(c *gin.Context) { rawBody, err := io.ReadAll(c.Request.Body) if err != nil { c.String(http.StatusBadRequest, "read failed") return } defer c.Request.Body.Close() if !turbodocx.VerifyWebhookSignature( rawBody, c.GetHeader("X-TurboDocx-Signature"), c.GetHeader("X-TurboDocx-Timestamp"), os.Getenv("TURBODOCX_WEBHOOK_SECRET"), nil, ) { c.String(http.StatusUnauthorized, "invalid signature") return } // dispatch event["eventType"] ... c.Status(http.StatusOK) }) r.Run(":3000") } ``` ```go package main "io" "net/http" "os" "github.com/labstack/echo/v4" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { e := echo.New() e.POST("/webhooks/turbodocx", func(c echo.Context) error { rawBody, err := io.ReadAll(c.Request().Body) if err != nil { return c.String(http.StatusBadRequest, "read failed") } defer c.Request().Body.Close() if !turbodocx.VerifyWebhookSignature( rawBody, c.Request().Header.Get("X-TurboDocx-Signature"), c.Request().Header.Get("X-TurboDocx-Timestamp"), os.Getenv("TURBODOCX_WEBHOOK_SECRET"), nil, ) { return c.String(http.StatusUnauthorized, "invalid signature") } // dispatch event["eventType"] ... return c.NoContent(http.StatusOK) }) e.Logger.Fatal(e.Start(":3000")) } ``` :::danger Use the raw request body The HMAC is computed over the **exact bytes** that left the TurboDocx server. Never decode JSON into a struct and re-marshal before verifying — re-encoded JSON will not byte-match and verification will fail. Always read with `io.ReadAll(r.Body)` first. ::: The signature contract: | Field | Value | |---|---| | Header | `X-TurboDocx-Signature: sha256=` | | Timestamp header | `X-TurboDocx-Timestamp: ` | | Signed string | `timestamp + "." + rawBody` | | Algorithm | HMAC-SHA256 | | Tolerance | 300 seconds (configurable via `VerifyWebhookSignatureOptions.ToleranceSeconds`) | | Comparison | `hmac.Equal` (constant-time) | ## Method Reference All methods are instance methods on `*turbodocx.WebhooksClient`. Construct once, then reuse. ### CreateWebhook Subscribe the org to events. Returns `*CreateWebhookResponse` with `ID` and `Secret` — the **secret is shown once**. ```go created, err := wh.CreateWebhook(ctx, turbodocx.CreateWebhookRequest{ URLs: []string{"https://your-server.example.com/webhooks/turbodocx"}, Events: []string{"signature.document.completed", "signature.document.voided"}, }) ``` | Error type | Why | |---|---| | `*ConflictError` (409) | The signature webhook already exists for this org. | | `*ValidationError` (400) | A URL is not HTTPS, or `Events` is empty. | | `*AuthorizationError` (403) | API key lacks the administrator role. | ### GetWebhook Get the org's signature webhook plus delivery statistics. Returns `map[string]interface{}` so new fields surface without an SDK upgrade. ```go webhook, err := wh.GetWebhook(ctx) // webhook["urls"], webhook["events"], webhook["isActive"] // webhook["deliveryStats"]: { totalDeliveries, successfulDeliveries, failedDeliveries, pendingRetries } // webhook["availableEvents"] ``` ### UpdateWebhook Patch one or more fields. Leave any field at its zero value to skip it. Use `turbodocx.BoolPtr(false)` to toggle `IsActive`. ```go updated, err := wh.UpdateWebhook(ctx, turbodocx.UpdateWebhookRequest{ URLs: []string{"https://your-server.example.com/webhooks/turbodocx"}, Events: []string{"signature.document.completed"}, IsActive: turbodocx.BoolPtr(true), }) ``` ### DeleteWebhook Soft-delete the webhook and its delivery history. ```go _, err := wh.DeleteWebhook(ctx) ``` ### TestWebhook Fire a synthetic delivery to every URL configured on the webhook. Useful for CI smoke tests before flipping a new receiver into production. ```go result, err := wh.TestWebhook(ctx, turbodocx.TestWebhookRequest{ EventType: "signature.document.completed", Payload: map[string]interface{}{ "documentId": "...", "documentName": "...", }, }) summary := result["summary"].(map[string]interface{}) fmt.Printf("%v/%v succeeded\n", summary["successful"], summary["total"]) if errs, ok := summary["errors"].([]interface{}); ok { for _, e := range errs { fmt.Printf(" failure: %v\n", e) // per-URL failure messages } } ``` `NotifyWebhook` is also exposed for symmetry with the backend surface — it routes through the same handler and returns the same shape. Prefer `TestWebhook` in new code. ### RegenerateWebhookSecret Rotate the HMAC secret. The new secret is shown **once**; old signatures fail immediately after rotation. ```go rotated, err := wh.RegenerateWebhookSecret(ctx) newSecret := rotated["secret"] // rotated["regeneratedAt"] ``` ### ListWebhookDeliveries Page through historical delivery attempts with filters. All filter fields are pointers — leave nil to skip. ```go limit := 20 delivered := false httpStatus := 500 page, err := wh.ListWebhookDeliveries(ctx, turbodocx.ListDeliveriesRequest{ Limit: &limit, EventType: "signature.document.completed", IsDelivered: &delivered, HTTPStatus: &httpStatus, }) // page["results"]: []interface{} (each element is a map[string]interface{} delivery row) // page["totalRecords"] ``` ### ReplayWebhookDelivery Manually retry a past delivery by ID. Returns a freshly-created delivery row. ```go replayed, err := wh.ReplayWebhookDelivery(ctx, "delivery-uuid-here") // replayed["id"], replayed["httpStatus"], replayed["attemptCount"], ... ``` ### GetWebhookStats Aggregate delivery stats over a sliding window. Pass `0` for the backend default (30 days). ```go stats, err := wh.GetWebhookStats(ctx, 30) // stats["summary"]["successRate"] // stats["summary"]["avgResponseTime"] (milliseconds) // stats["eventBreakdown"] (per-event totals) ``` ### VerifyWebhookSignature (free function) Verify the `X-TurboDocx-Signature` header on an incoming request. Exported directly from the package and does **not** require a `WebhooksClient` — receivers commonly run in a different process (or different deploy) than the management code. ```go ok := turbodocx.VerifyWebhookSignature( rawBody, // []byte — raw bytes as received signatureHeader, // value of X-TurboDocx-Signature timestampHeader, // value of X-TurboDocx-Timestamp webhookSecret, // the Secret from CreateWebhook nil, // *VerifyWebhookSignatureOptions — nil uses 300s tolerance ) ``` Pass `&turbodocx.VerifyWebhookSignatureOptions{ToleranceSeconds: 60}` to tighten the window, or `ToleranceSeconds: -1` to disable the timestamp check entirely (NOT recommended in production). ## Error Handling ```go _, err := wh.CreateWebhook(ctx, req) if err != nil { var conflict *turbodocx.ConflictError var valErr *turbodocx.ValidationError var authz *turbodocx.AuthorizationError var auth *turbodocx.AuthenticationError var nf *turbodocx.NotFoundError var rate *turbodocx.RateLimitError var netErr *turbodocx.NetworkError var tdx *turbodocx.TurboDocxError switch { case errors.As(err, &conflict): // 409 — signature webhook already exists; update or delete it instead case errors.As(err, &valErr): // 400 — non-HTTPS URL, empty events array, etc. case errors.As(err, &authz): // 403 — API key lacks the administrator role case errors.As(err, &auth): // 401 — bad or revoked API key case errors.As(err, &nf): // 404 — operating on a non-existent webhook case errors.As(err, &rate): // 429 — back off and retry case errors.As(err, &netErr): // request never reached the server (DNS, refused, timeout) case errors.As(err, &tdx): // catch-all for any other typed SDK error (raw 5xx, etc.) log.Printf("Error %d: %s", tdx.StatusCode, tdx.Message) } } ``` ### Common Error Codes | Status | Type | When | |---|---|---| | 400 | `*ValidationError` | Non-HTTPS URL, empty events, invalid body | | 401 | `*AuthenticationError` | Missing or invalid API key | | 403 | `*AuthorizationError` | Valid key without administrator role | | 404 | `*NotFoundError` | Operating on a non-existent webhook | | 409 | `*ConflictError` | Creating when the signature webhook already exists | | 429 | `*RateLimitError` | Rate limit exceeded — back off | ## Runnable End-to-End Example A complete, validated CRUD walkthrough lives in the SDK repo: **[`packages/go-sdk/examples/turbowebhooks_crud.go`](https://github.com/TurboDocx/SDK/blob/main/packages/go-sdk/examples/turbowebhooks_crud.go)** It exercises every CRUD step plus every error branch (400 / 401 / 403 / 404 / 409) against a live backend. Run with `go run examples/turbowebhooks_crud.go` after exporting `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID`. Override `TURBODOCX_RECEIVER_URL` to point at a real receiver (e.g. webhook.site, ngrok). ## Gotchas - **One webhook per org.** Every method targets the fixed-name `signature` webhook. Creating it twice returns `*ConflictError` (409). To manage multiple webhooks per org, call the REST API directly. - **Save the secret immediately.** `CreateWebhook` and `RegenerateWebhookSecret` return the HMAC secret **once**. There is no endpoint to retrieve it later. If you lose it, rotate. - **Use the raw bytes for verification.** The HMAC is over the exact request body received. Never unmarshal-then-remarshal first. Always `io.ReadAll(r.Body)` (or framework-equivalent) before calling `VerifyWebhookSignature`. - **`VerifyWebhookSignature` is a free function**, not a method on `WebhooksClient` — it has no `APIKey`/`OrgID` dependency. Pass `nil` for `opts` to use the default 300-second tolerance. - **Pointer-typed optional fields.** `UpdateWebhookRequest.IsActive` and `ListDeliveriesRequest.{Limit, Offset, IsDelivered, HTTPStatus}` are pointers so a zero value (`false` or `0`) can be told apart from "leave unchanged" / "no filter." Use `turbodocx.BoolPtr(false)` / `&n` to set them. - **`TestWebhook` summary now includes per-URL errors.** Type-assert `result["summary"].(map[string]interface{})["errors"].([]interface{})` to see exactly which receiver failed and why. ## See Also - [TurboSign → Webhooks](/docs/TurboSign/Webhooks) — concepts, dashboard UI, retry behavior - [TurboWebhooks JavaScript / TypeScript SDK](/docs/SDKs/webhooks-javascript) — same API, JS idioms - [TurboWebhooks Python SDK](/docs/SDKs/webhooks-python) — same API, Python idioms - [TurboWebhooks PHP SDK](/docs/SDKs/webhooks-php) — same API, PHP idioms - [TurboSign Go SDK](/docs/SDKs/go) — sending documents for signature - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk) --- # TurboWebhooks Java SDK The official TurboDocx Webhooks SDK for Java applications (Spring Boot, Servlet, Jakarta EE, etc.). Subscribe a single per-organization HTTPS endpoint to TurboDocx signature events, verify inbound signatures with HMAC-SHA256, replay delivery attempts, and rotate secrets — all from Java 11+. Distributed as `com.turbodocx:turbodocx-sdk` on Maven Central (same artifact as TurboSign). :::info What is TurboWebhooks? TurboWebhooks lets your application receive real-time notifications when signature documents complete or get voided, instead of polling the API. Each organization has a single, named webhook (`signature`) that mirrors the **Signature Webhooks** page in the dashboard, so SDK-managed and UI-managed configuration stays in sync. For the full conceptual overview of how webhooks work in TurboSign (delivery retries, payload schema, dashboard UI), see [TurboSign → Webhooks](/docs/TurboSign/Webhooks). ::: ## Installation ```xml com.turbodocx turbodocx-sdk 0.4.0 ``` ```groovy implementation 'com.turbodocx:turbodocx-sdk:0.4.0' ``` ```kotlin implementation("com.turbodocx:turbodocx-sdk:0.4.0") ``` Then import: ```java ``` ## Requirements - Java 11 or higher - An **administrator** TurboDocx API key (the webhook routes are gated on the administrator role — non-admin keys return HTTP 403) - All TurboWebhooks methods return `com.google.gson.JsonObject` for forward compatibility — new server fields surface without an SDK upgrade ## Configuration ```java TurboWebhooks webhooks = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .buildWebhooksClient(); ``` `buildWebhooksClient()` does **not** require `senderEmail` — webhook routes don't send email, so the sender validation that `build()` enforces for TurboSign is skipped here. The returned `TurboWebhooks` is an admin-scoped client; construct once and reuse. ### Environment Variables ```bash TURBODOCX_API_KEY=your_admin_api_key TURBODOCX_ORG_ID=your_org_id # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com # store the secret returned by createWebhook so your receiver can verify signatures TURBODOCX_WEBHOOK_SECRET=whsec_... ``` :::warning Administrator role required TurboWebhooks endpoints require the **administrator** role on the API key. A valid TDX- key without the role throws `TurboDocxException.AuthorizationException` (HTTP 403). Generate or rotate keys in the **Settings → API Keys** page. ::: ## Quick Start ### 1. Create the signature webhook ```java public class CreateSignatureWebhook { public static void main(String[] args) throws Exception { TurboWebhooks webhooks = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .buildWebhooksClient(); try { JsonObject created = webhooks.createWebhook( Arrays.asList("https://your-server.example.com/webhooks/turbodocx"), Arrays.asList("signature.document.completed", "signature.document.voided") ); // SAVE THIS SECRET — it is shown ONCE and cannot be retrieved later. Files.writeString(Paths.get(".secret"), created.get("secret").getAsString()); System.out.println("Created webhook id=" + created.get("id").getAsString()); } catch (TurboDocxException.ConflictException e) { // 409 — the signature webhook already exists for this org. // Use updateWebhook or deleteWebhook instead. System.out.println("Webhook already exists. Use updateWebhook or deleteWebhook."); } catch (TurboDocxException.ValidationException e) { // 400 — most commonly a non-HTTPS URL or empty events list. System.err.println("Validation failed: " + e.getMessage()); } } } ``` :::warning HTTPS only TurboDocx rejects non-HTTPS webhook URLs with HTTP 400. For local development, expose your receiver via an HTTPS tunnel ([ngrok](https://ngrok.com), [cloudflared](https://github.com/cloudflare/cloudflared), or [webhook.site](https://webhook.site)) and pass the tunnel URL to `createWebhook`. ::: ### 2. Verify inbound webhook signatures When TurboDocx POSTs to your receiver, every request carries an `X-TurboDocx-Signature` header. Verify it before trusting the payload — the helper enforces a 300-second timestamp tolerance and uses `MessageDigest.isEqual` for constant-time comparison. Java has no free functions, so the helper is exposed as `WebhookSignatureVerifier.verify(...)` — a static method on a final utility class. Semantically equivalent to the free-function form in JS / Py / Go / PHP. ```java package com.example.webhooks; @RestController public class TurboDocxWebhookController { @Value("${turbodocx.webhook.secret}") private String secret; // IMPORTANT: bind to byte[], not a parsed DTO. The signature is computed // over raw bytes — Jackson would re-serialize and whitespace mismatch // breaks HMAC verification. @PostMapping(value = "/webhooks/turbodocx", consumes = "application/json") public ResponseEntity receive( @RequestBody byte[] rawBody, @RequestHeader("X-TurboDocx-Signature") String signature, @RequestHeader("X-TurboDocx-Timestamp") String timestamp) { if (!WebhookSignatureVerifier.verify(rawBody, signature, timestamp, secret)) { return ResponseEntity.status(401).build(); } // Now safe to parse rawBody as JSON and dispatch on event.eventType. return ResponseEntity.ok().build(); } } ``` ```java package com.example.webhooks; @WebServlet("/webhooks/turbodocx") public class TurboDocxWebhookServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { // IMPORTANT: read raw bytes — never call getReader() or getParameter*, // those decode and re-encode the payload and break HMAC verification. byte[] rawBody = req.getInputStream().readAllBytes(); String signature = req.getHeader("X-TurboDocx-Signature"); String timestamp = req.getHeader("X-TurboDocx-Timestamp"); String secret = System.getenv("TURBODOCX_WEBHOOK_SECRET"); if (!WebhookSignatureVerifier.verify(rawBody, signature, timestamp, secret)) { resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid signature"); return; } // dispatch on event.eventType ... resp.setStatus(HttpServletResponse.SC_OK); } } ``` ```java package com.example.webhooks; @Path("/webhooks/turbodocx") public class TurboDocxWebhookResource { // JAX-RS deserializes byte[] entities as the raw request body — exactly // what HMAC verification needs. @POST public Response receive( byte[] rawBody, @HeaderParam("X-TurboDocx-Signature") String signature, @HeaderParam("X-TurboDocx-Timestamp") String timestamp) { String secret = System.getenv("TURBODOCX_WEBHOOK_SECRET"); if (!WebhookSignatureVerifier.verify(rawBody, signature, timestamp, secret)) { return Response.status(Response.Status.UNAUTHORIZED).build(); } // dispatch on event.eventType ... return Response.ok().build(); } } ``` :::danger Use the raw request body The HMAC is computed over the **exact bytes** that left the TurboDocx server. Never decode JSON into a DTO and re-serialize before verifying — re-encoded JSON will not byte-match and verification will fail. In Spring, bind to `@RequestBody byte[]`. In a Servlet, use `request.getInputStream().readAllBytes()`. In JAX-RS, declare a `byte[]` entity parameter. ::: The signature contract: | Field | Value | |---|---| | Header | `X-TurboDocx-Signature: sha256=` | | Timestamp header | `X-TurboDocx-Timestamp: ` | | Signed string | `timestamp + "." + rawBody` | | Algorithm | HMAC-SHA256 | | Tolerance | 300 seconds (configurable via the 6-arg `verify` overload) | | Comparison | `MessageDigest.isEqual` (constant-time) | ## Method Reference All methods are instance methods on `com.turbodocx.TurboWebhooks`. Construct once via `new TurboDocxClient.Builder()...buildWebhooksClient()` and reuse. ### createWebhook Subscribe the org to events. Returns a `JsonObject` with `id` and `secret` — the **secret is shown once**. ```java JsonObject created = webhooks.createWebhook( Arrays.asList("https://your-server.example.com/webhooks/turbodocx"), Arrays.asList("signature.document.completed", "signature.document.voided") ); ``` | Exception | Why | |---|---| | `TurboDocxException.ConflictException` (409) | The signature webhook already exists for this org. | | `TurboDocxException.ValidationException` (400) | A URL is not HTTPS, or the events list is empty. | | `TurboDocxException.AuthorizationException` (403) | API key lacks the administrator role. | ### getWebhook Get the org's signature webhook plus delivery statistics. ```java JsonObject webhook = webhooks.getWebhook(); // webhook.get("urls"), webhook.get("events"), webhook.get("isActive") // webhook.getAsJsonObject("deliveryStats"): // { totalDeliveries, successfulDeliveries, failedDeliveries, pendingRetries } // webhook.get("availableEvents") ``` ### updateWebhook Patch one or more fields. Pass `null` for any argument you don't want to change. Renaming is not supported. ```java JsonObject updated = webhooks.updateWebhook( Arrays.asList("https://your-server.example.com/webhooks/turbodocx"), // urls Arrays.asList("signature.document.completed"), // events Boolean.TRUE // isActive ); ``` ### deleteWebhook Soft-delete the webhook and its delivery history. ```java JsonObject deleted = webhooks.deleteWebhook(); ``` ### testWebhook Fire a synthetic delivery to every URL configured on the webhook. Useful for CI smoke tests before flipping a new receiver into production. ```java Map payload = new LinkedHashMap(); payload.put("documentId", "..."); payload.put("documentName", "..."); JsonObject result = webhooks.testWebhook("signature.document.completed", payload); JsonObject summary = result.getAsJsonObject("summary"); System.out.println(summary.get("successful") + "/" + summary.get("total") + " succeeded"); if (summary.has("errors") && summary.get("errors").isJsonArray()) { summary.getAsJsonArray("errors").forEach(err -> System.out.println(" failure: " + err)); // per-URL failure messages } ``` `notifyWebhook` is also exposed for symmetry with the backend surface — it routes through the same handler and returns the same shape. Prefer `testWebhook` in new code. ### regenerateWebhookSecret Rotate the HMAC secret. The new secret is shown **once**; old signatures fail immediately after rotation. ```java JsonObject rotated = webhooks.regenerateWebhookSecret(); String newSecret = rotated.get("secret").getAsString(); // rotated.get("regeneratedAt") ``` ### listWebhookDeliveries Page through historical delivery attempts with filters. Pass `null` for any filter to skip it; the no-arg overload skips all filters. ```java JsonObject page = webhooks.listWebhookDeliveries( 20, // limit null, // offset "signature.document.completed", // eventType Boolean.FALSE, // isDelivered 500 // httpStatus ); // page.getAsJsonArray("results") // page.get("totalRecords") ``` ### replayWebhookDelivery Manually retry a past delivery by ID. Returns a freshly-created delivery row. ```java JsonObject replayed = webhooks.replayWebhookDelivery("delivery-uuid-here"); // replayed.get("id"), replayed.get("httpStatus"), replayed.get("attemptCount"), ... ``` ### getWebhookStats Aggregate delivery stats over a sliding window. Pass `null` for the backend default (30 days). ```java JsonObject stats = webhooks.getWebhookStats(30); // stats.getAsJsonObject("summary").get("successRate") // stats.getAsJsonObject("summary").get("avgResponseTime") (milliseconds) // stats.get("eventBreakdown") (per-event totals) ``` ### WebhookSignatureVerifier.verify (static utility) Verify the `X-TurboDocx-Signature` header on an incoming request. Exposed as a static method on a final utility class — Java has no free functions, but the helper has no `apiKey` / `orgId` dependency, so it can be called from a receiver that runs in a completely different process (or deploy) than the management code. ```java boolean ok = WebhookSignatureVerifier.verify( rawBody, // byte[] — raw bytes as received signatureHeader, // value of X-TurboDocx-Signature timestampHeader, // value of X-TurboDocx-Timestamp webhookSecret // the secret from createWebhook ); ``` A `String` body overload is provided for convenience (`verify(String rawBody, ...)`), and a 6-arg overload accepts a custom `toleranceSeconds` plus an optional `LongSupplier now` for testing. Pass `toleranceSeconds = 0` to disable the timestamp check entirely (NOT recommended in production). ## Error Handling ```java try { webhooks.createWebhook(urls, events); } catch (TurboDocxException.ConflictException e) { // 409 — signature webhook already exists; update or delete it instead } catch (TurboDocxException.ValidationException e) { // 400 — non-HTTPS URL, empty events list, etc. } catch (TurboDocxException.AuthorizationException e) { // 403 — API key lacks the administrator role } catch (TurboDocxException.AuthenticationException e) { // 401 — bad or revoked API key } catch (TurboDocxException.NotFoundException e) { // 404 — operating on a non-existent webhook } catch (TurboDocxException.RateLimitException e) { // 429 — back off and retry } catch (TurboDocxException.NetworkException e) { // request never reached the server (DNS, refused, timeout) } catch (TurboDocxException e) { // catch-all for any other typed SDK error (raw 5xx, etc.) System.err.println("Error " + e.getStatusCode() + ": " + e.getMessage()); } ``` ### Common Error Codes | Status | Type | When | |---|---|---| | 400 | `TurboDocxException.ValidationException` | Non-HTTPS URL, empty events, invalid body | | 401 | `TurboDocxException.AuthenticationException` | Missing or invalid API key | | 403 | `TurboDocxException.AuthorizationException` | Valid key without administrator role | | 404 | `TurboDocxException.NotFoundException` | Operating on a non-existent webhook | | 409 | `TurboDocxException.ConflictException` | Creating when the signature webhook already exists | | 429 | `TurboDocxException.RateLimitException` | Rate limit exceeded — back off | ## Runnable End-to-End Example A complete, validated CRUD walkthrough lives in the SDK repo: **[`packages/java-sdk/examples/TurboWebhooksCrud.java`](https://github.com/TurboDocx/SDK/blob/main/packages/java-sdk/examples/TurboWebhooksCrud.java)** It exercises every CRUD step plus every error branch (400 / 401 / 403 / 404 / 409) against a live backend. Run it after exporting `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID`; override `TURBODOCX_RECEIVER_URL` to point at a real receiver (e.g. webhook.site, ngrok). ## Gotchas - **One webhook per org.** Every method targets the fixed-name `signature` webhook. Creating it twice throws `TurboDocxException.ConflictException` (409). To manage multiple webhooks per org, call the REST API directly. - **Save the secret immediately.** `createWebhook` and `regenerateWebhookSecret` return the HMAC secret **once**. There is no endpoint to retrieve it later. If you lose it, rotate. - **`WebhookSignatureVerifier` is a static utility** — Java has no free functions, so call it as `WebhookSignatureVerifier.verify(...)`. Final class with a private constructor; do not subclass. - **Use the raw bytes for verification.** The HMAC is over the exact request body received. In Spring, bind to `@RequestBody byte[] rawBody` — never `Map`/DTO; Jackson re-serialization breaks verification. In Servlets, use `request.getInputStream().readAllBytes()`. In JAX-RS, declare a `byte[]` entity parameter. - **Administrator role required.** The webhook routes are gated on `requireOrgRole(administrator)`. Valid TDX- keys without the role throw `TurboDocxException.AuthorizationException` (403). - **`null` skips fields on `updateWebhook` and `listWebhookDeliveries`.** Pass `null` for any argument you don't want to change/filter. - **`testWebhook` summary includes per-URL errors.** Read `result.getAsJsonObject("summary").getAsJsonArray("errors")` to see exactly which receiver failed and why. - **All TurboWebhooks methods return `JsonObject`.** New server fields surface without an SDK upgrade — use `.has(key)` / `.get(key)` to navigate. ## See Also - [TurboSign → Webhooks](/docs/TurboSign/Webhooks) — concepts, dashboard UI, retry behavior - [TurboWebhooks JavaScript / TypeScript SDK](/docs/SDKs/webhooks-javascript) — same API, JS idioms - [TurboWebhooks Python SDK](/docs/SDKs/webhooks-python) — same API, Python idioms - [TurboWebhooks Go SDK](/docs/SDKs/webhooks-go) — same API, Go idioms - [TurboWebhooks PHP SDK](/docs/SDKs/webhooks-php) — same API, PHP idioms - [TurboSign Java SDK](/docs/SDKs/java) — sending documents for signature - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk) --- # TurboWebhooks JavaScript / TypeScript SDK The official TurboDocx Webhooks SDK for Node.js applications (Express, Fastify, Next.js API routes, AWS Lambda, etc.). Subscribe a single per-organization HTTPS endpoint to TurboDocx signature events, verify inbound signatures with HMAC-SHA256, replay delivery attempts, and rotate secrets — all from Node 18+. Available on npm as `@turbodocx/sdk` (same package as TurboSign). :::info What is TurboWebhooks? TurboWebhooks lets your application receive real-time notifications when signature documents complete or get voided, instead of polling the API. Each organization has a single, named webhook (`signature`) that mirrors the **Signature Webhooks** page in the dashboard, so SDK-managed and UI-managed configuration stays in sync. For the full conceptual overview of how webhooks work in TurboSign (delivery retries, payload schema, dashboard UI), see [TurboSign → Webhooks](/docs/TurboSign/Webhooks). ::: ## Installation ```bash npm install @turbodocx/sdk ``` ```bash pnpm add @turbodocx/sdk ``` ```bash yarn add @turbodocx/sdk ``` ## Requirements - Node.js 18 or higher (native `fetch` + `crypto.timingSafeEqual`) - An **administrator** TurboDocx API key (the webhook routes are gated on the administrator role — non-admin keys return HTTP 403) - Zero runtime dependencies — the SDK only uses Node built-ins ## Configuration ```typescript TurboWebhooks.configure({ apiKey: process.env.TURBODOCX_API_KEY!, orgId: process.env.TURBODOCX_ORG_ID!, }); ``` `skipSenderValidation: true` is hardcoded inside `TurboWebhooks.configure()` because webhooks don't send email — only TurboSign needs `senderEmail`. If you skip the explicit call, the SDK lazily configures itself from `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID` on first method invocation. ### Environment Variables ```bash TURBODOCX_API_KEY=your_admin_api_key TURBODOCX_ORG_ID=your_org_id # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com # store the secret returned by createWebhook so your receiver can verify signatures TURBODOCX_WEBHOOK_SECRET=whsec_... ``` :::warning Administrator role required TurboWebhooks endpoints require the **administrator** role on the API key. A valid TDX- key without the role will throw `AuthorizationError` (HTTP 403). Generate or rotate keys in the **Settings → API Keys** page. ::: ## Quick Start ### 1. Create the signature webhook ```typescript TurboWebhooks, ConflictError, ValidationError, } from '@turbodocx/sdk'; TurboWebhooks.configure({ apiKey: process.env.TURBODOCX_API_KEY!, orgId: process.env.TURBODOCX_ORG_ID!, }); try { const created = await TurboWebhooks.createWebhook({ urls: ['https://your-server.example.com/webhooks/turbodocx'], events: ['signature.document.completed', 'signature.document.voided'], }); // SAVE THIS SECRET — it is shown ONCE and cannot be retrieved later. writeFileSync('.secret', created.secret, { mode: 0o600 }); console.log(`Created webhook id=${created.id}`); } catch (e) { if (e instanceof ConflictError) { // 409 — the signature webhook already exists for this org. // Use TurboWebhooks.updateWebhook(...) or .deleteWebhook() instead. console.log('Webhook already exists. Use updateWebhook or deleteWebhook.'); } else if (e instanceof ValidationError) { // 400 — most commonly a non-HTTPS URL or empty events array. console.log(`Validation failed: ${e.message}`); } else { throw e; } } ``` :::warning HTTPS only TurboDocx rejects non-HTTPS webhook URLs with HTTP 400. For local development, expose your receiver via an HTTPS tunnel ([ngrok](https://ngrok.com), [cloudflared](https://github.com/cloudflare/cloudflared), or [webhook.site](https://webhook.site)) and pass the tunnel URL to `createWebhook`. ::: ### 2. Verify inbound webhook signatures When TurboDocx POSTs to your receiver, every request carries an `X-TurboDocx-Signature` header. Verify it before trusting the payload — the helper enforces a 300-second timestamp tolerance and uses `crypto.timingSafeEqual` for constant-time comparison. ```typescript const app = express(); // IMPORTANT: use express.raw — the signature is computed over raw bytes. // express.json() will mangle whitespace and break verification. app.post( '/webhooks/turbodocx', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.header('x-turbodocx-signature') ?? ''; const timestamp = req.header('x-turbodocx-timestamp') ?? ''; const secret = process.env.TURBODOCX_WEBHOOK_SECRET!; if (!verifyWebhookSignature(req.body, signature, timestamp, secret)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse((req.body as Buffer).toString('utf8')); // process event.eventType, event.data, ... res.status(200).send('ok'); }, ); ``` :::danger Use the raw request body The HMAC is computed over the **exact bytes** that left the TurboDocx server. Never `JSON.parse` and re-stringify before verifying — re-encoded JSON will not byte-match and verification will fail. Use `express.raw()`, Fastify's `rawBody` option, or Next.js Edge's `await request.text()`. ::: The signature contract: | Field | Value | |---|---| | Header | `X-TurboDocx-Signature: sha256=` | | Timestamp header | `X-TurboDocx-Timestamp: ` | | Signed string | `${timestamp}.${rawBody}` | | Algorithm | HMAC-SHA256 | | Tolerance | 300 seconds (configurable) | | Comparison | `crypto.timingSafeEqual` (constant-time) | ## Method Reference All methods are static; configure once, then call on the `TurboWebhooks` class. ### createWebhook Subscribe the org to events. Returns `{id, secret}` — the **secret is shown once**. ```typescript const created = await TurboWebhooks.createWebhook({ urls: ['https://your-server.example.com/webhooks/turbodocx'], events: ['signature.document.completed', 'signature.document.voided'], }); ``` | Throws | Why | |---|---| | `ConflictError` (409) | The signature webhook already exists for this org. | | `ValidationError` (400) | A URL is not HTTPS, or `events` is empty. | | `AuthorizationError` (403) | API key lacks the administrator role. | ### getWebhook Get the org's signature webhook plus delivery statistics. ```typescript const webhook = await TurboWebhooks.getWebhook(); // webhook.urls, webhook.events, webhook.isActive // webhook.deliveryStats.{totalDeliveries, successfulDeliveries, failedDeliveries, pendingRetries} // webhook.availableEvents ``` ### updateWebhook Patch one or more fields. All fields are optional — pass only what changes. ```typescript await TurboWebhooks.updateWebhook({ urls: ['https://your-server.example.com/webhooks/turbodocx'], events: ['signature.document.completed'], isActive: true, }); ``` ### deleteWebhook Soft-delete the webhook and its delivery history. ```typescript await TurboWebhooks.deleteWebhook(); ``` ### testWebhook Fire a synthetic delivery to every URL configured on the webhook. Useful for CI smoke tests before flipping a new receiver into production. ```typescript const result = await TurboWebhooks.testWebhook({ eventType: 'signature.document.completed', payload: { documentId: '...', documentName: '...' }, }); console.log(`${result.summary.successful}/${result.summary.total} succeeded`); for (const err of result.summary.errors) { console.log(` failure: ${err}`); // per-URL failure messages } ``` ### notifyWebhook Manually send a notification to all URLs configured on the webhook. Routes through the same backend handler as `testWebhook` and returns an identical response shape. Exposed for symmetry with the backend surface; prefer `testWebhook` in new code. ```typescript const result = await TurboWebhooks.notifyWebhook({ eventType: 'signature.document.completed', payload: { documentId: '...', documentName: '...' }, }); console.log(`${result.summary.successful}/${result.summary.total} succeeded`); for (const err of result.summary.errors) { console.log(` failure: ${err}`); // per-URL failure messages } ``` ### regenerateWebhookSecret Rotate the HMAC secret. The new secret is shown **once**; old signatures fail immediately after rotation. ```typescript const rotated = await TurboWebhooks.regenerateWebhookSecret(); // rotated.secret // rotated.regeneratedAt ``` ### listWebhookDeliveries Page through historical delivery attempts with filters. ```typescript const page = await TurboWebhooks.listWebhookDeliveries({ limit: 20, offset: 0, eventType: 'signature.document.completed', isDelivered: false, httpStatus: 500, }); // page.results: WebhookDelivery[] // page.totalRecords ``` ### replayWebhookDelivery Manually retry a past delivery by ID. Returns a freshly-created delivery row. ```typescript const replay = await TurboWebhooks.replayWebhookDelivery('delivery-uuid-here'); // replay.id, replay.httpStatus, replay.attemptCount, ... ``` ### getWebhookStats Aggregate delivery stats over a sliding window. ```typescript const stats = await TurboWebhooks.getWebhookStats({ days: 30 }); // stats.summary.successRate // stats.summary.avgResponseTime (milliseconds) // stats.eventBreakdown (per-event totals) ``` ### verifyWebhookSignature (free function) Verify the `X-TurboDocx-Signature` header on an incoming request. Exported directly from `@turbodocx/sdk` and does **not** require `TurboWebhooks.configure()` — receivers commonly run in a different process (or different deploy) than the management code. ```typescript const ok = verifyWebhookSignature( rawBody, // string | Buffer — raw bytes as received signatureHeader, // value of X-TurboDocx-Signature timestampHeader, // value of X-TurboDocx-Timestamp webhookSecret, // the secret from createWebhook { toleranceSeconds: 300 }, // default; pass 0 to disable timestamp check (NOT recommended) ); ``` ## Framework Examples ```typescript // server.ts TurboWebhooks.configure({ apiKey: process.env.TURBODOCX_API_KEY!, orgId: process.env.TURBODOCX_ORG_ID!, }); const app = express(); // Receiver — MUST use express.raw, not express.json app.post( '/webhooks/turbodocx', express.raw({ type: 'application/json' }), (req, res) => { const ok = verifyWebhookSignature( req.body, req.header('x-turbodocx-signature') ?? '', req.header('x-turbodocx-timestamp') ?? '', process.env.TURBODOCX_WEBHOOK_SECRET!, ); if (!ok) return res.status(401).send('Invalid signature'); const event = JSON.parse((req.body as Buffer).toString('utf8')); switch (event.eventType) { case 'signature.document.completed': /* ... */ break; case 'signature.document.voided': /* ... */ break; } res.status(200).send('ok'); }, ); app.listen(3000); ``` ```typescript // app/api/webhooks/turbodocx/route.ts export async function POST(req: NextRequest) { // Read raw bytes BEFORE parsing — required for HMAC verification. const rawBody = await req.text(); const ok = verifyWebhookSignature( rawBody, req.headers.get('x-turbodocx-signature') ?? '', req.headers.get('x-turbodocx-timestamp') ?? '', process.env.TURBODOCX_WEBHOOK_SECRET!, ); if (!ok) return new NextResponse('Invalid signature', { status: 401 }); const event = JSON.parse(rawBody); // dispatch event.eventType ... return NextResponse.json({ ok: true }); } ``` ```typescript // server.ts const app = Fastify(); // Capture raw body for signature verification app.addContentTypeParser( 'application/json', { parseAs: 'buffer' }, (_req, body, done) => done(null, body), ); app.post('/webhooks/turbodocx', (req, reply) => { const rawBody = req.body as Buffer; const ok = verifyWebhookSignature( rawBody, (req.headers['x-turbodocx-signature'] as string) ?? '', (req.headers['x-turbodocx-timestamp'] as string) ?? '', process.env.TURBODOCX_WEBHOOK_SECRET!, ); if (!ok) return reply.code(401).send('Invalid signature'); const event = JSON.parse(rawBody.toString('utf8')); // dispatch ... reply.code(200).send('ok'); }); app.listen({ port: 3000 }); ``` ## Error Handling ```typescript TurboDocxError, AuthenticationError, AuthorizationError, ValidationError, ConflictError, NotFoundError, RateLimitError, NetworkError, } from '@turbodocx/sdk'; try { await TurboWebhooks.createWebhook({ urls, events }); } catch (e) { if (e instanceof ConflictError) { // 409 — signature webhook already exists; update or delete it instead } else if (e instanceof ValidationError) { // 400 — non-HTTPS URL, empty events array, etc. } else if (e instanceof AuthorizationError) { // 403 — API key lacks the administrator role } else if (e instanceof AuthenticationError) { // 401 — bad or revoked API key } else if (e instanceof NotFoundError) { // 404 — operating on a non-existent webhook } else if (e instanceof RateLimitError) { // 429 — back off and retry } else if (e instanceof NetworkError) { // request never reached the server (DNS, refused, timeout) } else if (e instanceof TurboDocxError) { // catch-all for any other typed SDK error (raw 5xx, etc.) console.error(`Error ${e.statusCode}: ${e.message}`); } else { throw e; } } ``` ### Common Error Codes | Status | Class | When | |---|---|---| | 400 | `ValidationError` | Non-HTTPS URL, empty events, invalid body | | 401 | `AuthenticationError` | Missing or invalid API key | | 403 | `AuthorizationError` | Valid key without administrator role | | 404 | `NotFoundError` | Operating on a non-existent webhook | | 409 | `ConflictError` | Creating when the signature webhook already exists | | 429 | `RateLimitError` | Rate limit exceeded — back off | ## Runnable End-to-End Example A complete, validated CRUD walkthrough lives in the SDK repo: **[`packages/js-sdk/examples/turbowebhooks-crud.ts`](https://github.com/TurboDocx/SDK/blob/main/packages/js-sdk/examples/turbowebhooks-crud.ts)** It exercises every CRUD step plus every error branch (400 / 401 / 403 / 404 / 409) against a live backend. Run with `npx tsx examples/turbowebhooks-crud.ts` after exporting `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID`. Override `TURBODOCX_RECEIVER_URL` to point at a real receiver (e.g. webhook.site, ngrok). ## Gotchas - **One webhook per org.** Every method targets the fixed-name `signature` webhook. Creating it twice returns `ConflictError` (409). To manage multiple webhooks per org, call the REST API directly. - **Save the secret immediately.** `createWebhook` and `regenerateWebhookSecret` return the HMAC secret **once**. There is no endpoint to retrieve it later. If you lose it, rotate. - **Use the raw bytes for verification.** The HMAC is over the exact request body received. Never `JSON.parse` first. In Express, use `express.raw({ type: 'application/json' })`; in Next.js, `await req.text()`; in Fastify, register a raw-body content-type parser. - **`verifyWebhookSignature` is a free function**, not a method on `TurboWebhooks` — import it directly from `@turbodocx/sdk`. It has no `apiKey`/`orgId` dependency. - **`replayWebhookDelivery` returns the full delivery row.** Earlier SDK versions documented a partial shape — current versions return the complete `WebhookDelivery` object. - **`testWebhook` summary now includes per-URL errors.** Check `result.summary.errors` to see exactly which receiver failed and why. ## See Also - [TurboSign → Webhooks](/docs/TurboSign/Webhooks) — concepts, dashboard UI, retry behavior - [TurboWebhooks PHP SDK](/docs/SDKs/webhooks-php) — same API, PHP idioms - [TurboSign JavaScript SDK](/docs/SDKs/javascript) — sending documents for signature - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [@turbodocx/sdk on npm](https://www.npmjs.com/package/@turbodocx/sdk) - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/js-sdk) --- # TurboWebhooks PHP SDK The official TurboDocx Webhooks SDK for PHP applications. Subscribe a single per-organization HTTPS endpoint to TurboDocx signature events, verify inbound signatures with HMAC-SHA256, replay delivery attempts, and rotate secrets — all from PHP 8.1+. Available on Packagist as `turbodocx/sdk` (same package as TurboSign). :::info What is TurboWebhooks? TurboWebhooks lets your application receive real-time notifications when signature documents complete or get voided, instead of polling the API. Each organization has a single, named webhook (`signature`) that mirrors the **Signature Webhooks** page in the dashboard, so SDK-managed and UI-managed configuration stays in sync. For the full conceptual overview of how webhooks work in TurboSign (delivery retries, payload schema, dashboard UI), see [TurboSign → Webhooks](/docs/TurboSign/Webhooks). ::: ## Installation ```bash composer require turbodocx/sdk ``` ## Requirements - PHP 8.1 or higher - Composer 2.x - An **administrator** TurboDocx API key (the webhook routes are gated on the administrator role — non-admin keys return HTTP 403) ## Configuration ```php getMessage()}\n"; } ``` :::warning HTTPS only TurboDocx rejects non-HTTPS webhook URLs with HTTP 400. For local development, expose your receiver via an HTTPS tunnel ([ngrok](https://ngrok.com), [cloudflared](https://github.com/cloudflare/cloudflared), etc.) and pass the tunnel URL to `createWebhook`. ::: ### 2. Verify inbound webhook signatures When TurboDocx POSTs to your receiver, every request carries an `X-TurboDocx-Signature` header. Verify it before trusting the payload — the helper enforces a 300-second timestamp tolerance and uses constant-time comparison. ```php ` | | Timestamp header | `X-TurboDocx-Timestamp: ` | | Signed string | `${timestamp}.${rawBody}` | | Algorithm | HMAC-SHA256 | | Tolerance | 300 seconds (configurable) | | Comparison | `hash_equals` (constant-time) | ## Method Reference All methods are static; configure once, then call on the `TurboWebhooks` class. ### createWebhook Subscribe the org to events. Returns `{id, secret}` — the **secret is shown once**. ```php $created = TurboWebhooks::createWebhook( urls: ['https://your-server.example.com/webhooks/turbodocx'], events: ['signature.document.completed', 'signature.document.voided'], ); ``` | Throws | Why | |---|---| | `ConflictException` (409) | The signature webhook already exists for this org. | | `ValidationException` (400) | A URL is not HTTPS, or `events` is empty. | | `AuthorizationException` (403) | API key lacks the administrator role. | ### getWebhook Get the org's signature webhook plus delivery statistics. ```php $webhook = TurboWebhooks::getWebhook(); // $webhook['urls'], $webhook['events'], $webhook['isActive'] // $webhook['deliveryStats']['totalDeliveries'] // $webhook['deliveryStats']['successfulDeliveries'] // $webhook['deliveryStats']['failedDeliveries'] // $webhook['deliveryStats']['pendingRetries'] // $webhook['availableEvents'] ``` ### updateWebhook Patch one or more fields. All parameters are optional — pass only what changes. ```php TurboWebhooks::updateWebhook( urls: ['https://your-server.example.com/webhooks/turbodocx'], events: ['signature.document.completed'], isActive: true, ); ``` ### deleteWebhook Soft-delete the webhook and its delivery history. ```php TurboWebhooks::deleteWebhook(); ``` ### testWebhook Fire a synthetic delivery to every URL configured on the webhook. Useful for CI smoke tests before flipping a new receiver into production. ```php $result = TurboWebhooks::testWebhook( eventType: 'signature.document.completed', payload: ['documentId' => '...', 'documentName' => '...'], ); echo "{$result['summary']['successful']}/{$result['summary']['total']} succeeded\n"; foreach ($result['summary']['errors'] as $err) { echo " failure: {$err}\n"; // per-URL failure messages } ``` ### regenerateWebhookSecret Rotate the HMAC secret. The new secret is shown **once**; old signatures fail immediately after rotation. ```php $rotated = TurboWebhooks::regenerateWebhookSecret(); // $rotated['secret'] // $rotated['regeneratedAt'] ``` ### listWebhookDeliveries Page through historical delivery attempts with filters. ```php $page = TurboWebhooks::listWebhookDeliveries( limit: 20, offset: 0, eventType: 'signature.document.completed', isDelivered: false, httpStatus: 500, ); // $page['results']: WebhookDelivery[] // $page['totalRecords'] ``` ### replayWebhookDelivery Manually retry a past delivery by ID. Returns a freshly-created delivery row. ```php $replay = TurboWebhooks::replayWebhookDelivery('delivery-uuid-here'); // $replay['id'], $replay['httpStatus'], $replay['attemptCount'], ... ``` ### getWebhookStats Aggregate delivery stats over a sliding window. ```php $stats = TurboWebhooks::getWebhookStats(days: 30); // $stats['summary']['successRate'] // $stats['summary']['avgResponseTime'] (milliseconds) // $stats['eventBreakdown'] (per-event totals) ``` ### verifyWebhookSignature (free function) Verify the `X-TurboDocx-Signature` header on an incoming request. Lives in the `TurboDocx\Utils` namespace and does **not** require `TurboWebhooks::configure()` — receivers commonly run in a different process than the management code. ```php use function TurboDocx\Utils\verifyWebhookSignature; $ok = verifyWebhookSignature( rawBody: $rawBody, signatureHeader: $signatureHeader, timestampHeader: $timestampHeader, secret: $webhookSecret, toleranceSeconds: 300, // default; pass 0 to disable timestamp check (NOT recommended) ); ``` ## Laravel Integration Example Set up TurboWebhooks once in a service provider and add a controller for the receiver. ```php // app/Providers/TurboDocxServiceProvider.php namespace App\Providers; use Illuminate\Support\ServiceProvider; use TurboDocx\TurboWebhooks; use TurboDocx\Config\HttpClientConfig; class TurboDocxServiceProvider extends ServiceProvider { public function boot(): void { TurboWebhooks::configure(new HttpClientConfig( apiKey: config('services.turbodocx.api_key'), orgId: config('services.turbodocx.org_id'), skipSenderValidation: true, )); } } ``` ```php // app/Http/Controllers/WebhookController.php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\Response; use function TurboDocx\Utils\verifyWebhookSignature; class WebhookController extends Controller { public function handle(Request $request): Response { $rawBody = $request->getContent(); $signatureHeader = $request->header('X-TurboDocx-Signature', ''); $timestampHeader = $request->header('X-TurboDocx-Timestamp', ''); $secret = config('services.turbodocx.webhook_secret'); if (!verifyWebhookSignature($rawBody, $signatureHeader, $timestampHeader, $secret)) { return response('', 401); } $event = json_decode($rawBody, true); match ($event['eventType']) { 'signature.document.completed' => $this->onCompleted($event['data']), 'signature.document.voided' => $this->onVoided($event['data']), default => null, }; return response('', 200); } } ``` ```php // routes/web.php (or routes/api.php) Route::post('/webhooks/turbodocx', [WebhookController::class, 'handle']); ``` ## Error Handling ```php use TurboDocx\Exceptions\TurboDocxException; use TurboDocx\Exceptions\AuthenticationException; use TurboDocx\Exceptions\AuthorizationException; use TurboDocx\Exceptions\ValidationException; use TurboDocx\Exceptions\ConflictException; use TurboDocx\Exceptions\NotFoundException; use TurboDocx\Exceptions\RateLimitException; use TurboDocx\Exceptions\NetworkException; try { TurboWebhooks::createWebhook(urls: $urls, events: $events); } catch (ConflictException $e) { // 409 — signature webhook already exists; update or delete it instead } catch (ValidationException $e) { // 400 — non-HTTPS URL, empty events array, etc. } catch (AuthorizationException $e) { // 403 — API key lacks the administrator role } catch (AuthenticationException $e) { // 401 — bad or revoked API key } catch (NotFoundException $e) { // 404 — read/update/delete against a webhook that doesn't exist } catch (RateLimitException $e) { // 429 — back off and retry } catch (NetworkException $e) { // request never reached the server (DNS, refused, timeout) } catch (TurboDocxException $e) { // catch-all for any other typed SDK error (raw 5xx, etc.) echo "Error {$e->statusCode}: {$e->getMessage()}\n"; } ``` ### Common Error Codes | Status | Exception | When | |---|---|---| | 400 | `ValidationException` | Non-HTTPS URL, empty events, invalid body | | 401 | `AuthenticationException` | Missing or invalid API key | | 403 | `AuthorizationException` | Valid key without administrator role | | 404 | `NotFoundException` | Operating on a non-existent webhook | | 409 | `ConflictException` | Creating when the signature webhook already exists | | 429 | `RateLimitException` | Rate limit exceeded — back off | ## Runnable End-to-End Example A complete, validated CRUD walkthrough lives in the SDK repo: **[`packages/php-sdk/examples/turbowebhooks-crud.php`](https://github.com/TurboDocx/SDK/blob/main/packages/php-sdk/examples/turbowebhooks-crud.php)** It exercises every CRUD step plus every error branch (400 / 401 / 403 / 404 / 409) against a live backend. ## Gotchas - **One webhook per org.** Every method targets the fixed-name `signature` webhook. Creating it twice returns `ConflictException` (409). To manage multiple webhooks per org, call the REST API directly. - **Save the secret immediately.** `createWebhook` and `regenerateWebhookSecret` return the HMAC secret **once**. There is no endpoint to retrieve it later. If you lose it, rotate. - **Use the raw bytes for verification.** The HMAC is over the exact request body received. Never `json_decode` first. - **`replayWebhookDelivery` returns the full delivery row.** Earlier SDK versions documented a partial shape (`{id, httpStatus, message}`) — current versions return the complete `WebhookDelivery` object. - **`testWebhook` summary now includes per-URL errors.** Check `$result['summary']['errors']` to see exactly which receiver failed and why. - **`createWebhook` and `regenerateWebhookSecret` return data without a `message` field.** The success message lives at the response envelope and is extracted away by the SDK. ## See Also - [TurboSign → Webhooks](/docs/TurboSign/Webhooks) — concepts, dashboard UI, retry behavior - [TurboSign PHP SDK](/docs/SDKs/php) — sending documents for signature - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [TurboDocx SDK on Packagist](https://packagist.org/packages/turbodocx/sdk) - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/php-sdk) --- # TurboWebhooks Python SDK The official TurboDocx Webhooks SDK for Python applications (Flask, FastAPI, Django, AWS Lambda, etc.). Subscribe a single per-organization HTTPS endpoint to TurboDocx signature events, verify inbound signatures with HMAC-SHA256, replay delivery attempts, and rotate secrets — all from Python 3.9+. Distributed on PyPI as `turbodocx-sdk` (same package as TurboSign). :::info What is TurboWebhooks? TurboWebhooks lets your application receive real-time notifications when signature documents complete or get voided, instead of polling the API. Each organization has a single, named webhook (`signature`) that mirrors the **Signature Webhooks** page in the dashboard, so SDK-managed and UI-managed configuration stays in sync. For the full conceptual overview of how webhooks work in TurboSign (delivery retries, payload schema, dashboard UI), see [TurboSign → Webhooks](/docs/TurboSign/Webhooks). ::: ## Installation ```bash pip install turbodocx-sdk ``` ```bash poetry add turbodocx-sdk ``` ```bash pipenv install turbodocx-sdk ``` ## Requirements - Python 3.9 or higher - An **administrator** TurboDocx API key (the webhook routes are gated on the administrator role — non-admin keys return HTTP 403) - All SDK methods are `async` — call them from an `async def` (or wrap with `asyncio.run(...)` in synchronous contexts) ## Configuration ```python from turbodocx_sdk import TurboWebhooks TurboWebhooks.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], ) ``` `skip_sender_validation=True` is hardcoded inside `TurboWebhooks.configure()` because webhooks don't send email — only TurboSign needs `sender_email`. If you skip the explicit call, the SDK lazily configures itself from `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID` on first method invocation. ### Environment Variables ```bash TURBODOCX_API_KEY=your_admin_api_key TURBODOCX_ORG_ID=your_org_id # optional — defaults to https://api.turbodocx.com TURBODOCX_BASE_URL=https://api.turbodocx.com # store the secret returned by create_webhook so your receiver can verify signatures TURBODOCX_WEBHOOK_SECRET=whsec_... ``` :::warning Administrator role required TurboWebhooks endpoints require the **administrator** role on the API key. A valid TDX- key without the role will raise `AuthorizationError` (HTTP 403). Generate or rotate keys in the **Settings → API Keys** page. ::: ## Quick Start ### 1. Create the signature webhook ```python from turbodocx_sdk import ( TurboWebhooks, ConflictError, ValidationError, ) async def setup_webhook(): TurboWebhooks.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], ) try: created = await TurboWebhooks.create_webhook( urls=["https://your-server.example.com/webhooks/turbodocx"], events=["signature.document.completed", "signature.document.voided"], ) # SAVE THIS SECRET — it is shown ONCE and cannot be retrieved later. with open(".secret", "w") as f: f.write(created["secret"]) os.chmod(".secret", 0o600) print(f"Created webhook id={created['id']}") except ConflictError: # 409 — the signature webhook already exists for this org. # Use TurboWebhooks.update_webhook(...) or .delete_webhook() instead. print("Webhook already exists. Use update_webhook or delete_webhook.") except ValidationError as e: # 400 — most commonly a non-HTTPS URL or empty events array. print(f"Validation failed: {e}") asyncio.run(setup_webhook()) ``` :::warning HTTPS only TurboDocx rejects non-HTTPS webhook URLs with HTTP 400. For local development, expose your receiver via an HTTPS tunnel ([ngrok](https://ngrok.com), [cloudflared](https://github.com/cloudflare/cloudflared), or [webhook.site](https://webhook.site)) and pass the tunnel URL to `create_webhook`. ::: ### 2. Verify inbound webhook signatures When TurboDocx POSTs to your receiver, every request carries an `X-TurboDocx-Signature` header. Verify it before trusting the payload — the helper enforces a 300-second timestamp tolerance and uses `hmac.compare_digest` for constant-time comparison. ```python from flask import Flask, request, abort from turbodocx_sdk import verify_webhook_signature app = Flask(__name__) @app.post("/webhooks/turbodocx") def turbodocx_webhook(): # IMPORTANT: read raw bytes — the signature is computed over them. # request.get_json() will mangle whitespace and break verification. raw_body = request.get_data() signature = request.headers.get("X-TurboDocx-Signature", "") timestamp = request.headers.get("X-TurboDocx-Timestamp", "") secret = os.environ["TURBODOCX_WEBHOOK_SECRET"] if not verify_webhook_signature(raw_body, signature, timestamp, secret): abort(401, "Invalid signature") event = json.loads(raw_body) # process event["eventType"], event["data"], ... return ("ok", 200) ``` ```python from fastapi import FastAPI, Request, HTTPException from turbodocx_sdk import verify_webhook_signature app = FastAPI() @app.post("/webhooks/turbodocx") async def turbodocx_webhook(request: Request): # IMPORTANT: read raw bytes — the signature is computed over them. # await request.json() will mangle whitespace and break verification. raw_body = await request.body() signature = request.headers.get("x-turbodocx-signature", "") timestamp = request.headers.get("x-turbodocx-timestamp", "") secret = os.environ["TURBODOCX_WEBHOOK_SECRET"] if not verify_webhook_signature(raw_body, signature, timestamp, secret): raise HTTPException(status_code=401, detail="Invalid signature") event = json.loads(raw_body) # process event["eventType"], event["data"], ... return {"ok": True} ``` :::danger Use the raw request body The HMAC is computed over the **exact bytes** that left the TurboDocx server. Never call `json.loads(...)` and re-serialize before verifying — re-encoded JSON will not byte-match and verification will fail. Use Flask's `request.get_data()`, FastAPI's `await request.body()`, or Django's `request.body`. ::: The signature contract: | Field | Value | |---|---| | Header | `X-TurboDocx-Signature: sha256=` | | Timestamp header | `X-TurboDocx-Timestamp: ` | | Signed string | `f"{timestamp}.{raw_body}"` | | Algorithm | HMAC-SHA256 | | Tolerance | 300 seconds (configurable) | | Comparison | `hmac.compare_digest` (constant-time) | ## Method Reference All methods are `@classmethod`s on `TurboWebhooks`; configure once, then call on the class. ### create_webhook Subscribe the org to events. Returns a dict with `id` and `secret` — the **secret is shown once**. ```python created = await TurboWebhooks.create_webhook( urls=["https://your-server.example.com/webhooks/turbodocx"], events=["signature.document.completed", "signature.document.voided"], ) ``` | Raises | Why | |---|---| | `ConflictError` (409) | The signature webhook already exists for this org. | | `ValidationError` (400) | A URL is not HTTPS, or `events` is empty. | | `AuthorizationError` (403) | API key lacks the administrator role. | ### get_webhook Get the org's signature webhook plus delivery statistics. ```python webhook = await TurboWebhooks.get_webhook() # webhook["urls"], webhook["events"], webhook["isActive"] # webhook["deliveryStats"]: {"totalDeliveries", "successfulDeliveries", "failedDeliveries", "pendingRetries"} # webhook["availableEvents"] ``` ### update_webhook Patch one or more fields. All fields are keyword-only and optional — pass only what changes. ```python await TurboWebhooks.update_webhook( urls=["https://your-server.example.com/webhooks/turbodocx"], events=["signature.document.completed"], is_active=True, ) ``` ### delete_webhook Soft-delete the webhook and its delivery history. ```python await TurboWebhooks.delete_webhook() ``` ### test_webhook Fire a synthetic delivery to every URL configured on the webhook. Useful for CI smoke tests before flipping a new receiver into production. ```python result = await TurboWebhooks.test_webhook( event_type="signature.document.completed", payload={"documentId": "...", "documentName": "..."}, ) print(f"{result['summary']['successful']}/{result['summary']['total']} succeeded") for err in result["summary"].get("errors", []): print(f" failure: {err}") # per-URL failure messages ``` ### regenerate_webhook_secret Rotate the HMAC secret. The new secret is shown **once**; old signatures fail immediately after rotation. ```python rotated = await TurboWebhooks.regenerate_webhook_secret() # rotated["secret"] # rotated["regeneratedAt"] ``` ### list_webhook_deliveries Page through historical delivery attempts with filters. ```python page = await TurboWebhooks.list_webhook_deliveries( limit=20, offset=0, event_type="signature.document.completed", is_delivered=False, http_status=500, ) # page["results"]: list of WebhookDelivery # page["totalRecords"] ``` ### replay_webhook_delivery Manually retry a past delivery by ID. Returns a freshly-created delivery row. ```python replay = await TurboWebhooks.replay_webhook_delivery("delivery-uuid-here") # replay["id"], replay["httpStatus"], replay["attemptCount"], ... ``` ### get_webhook_stats Aggregate delivery stats over a sliding window. ```python stats = await TurboWebhooks.get_webhook_stats(days=30) # stats["summary"]["successRate"] # stats["summary"]["avgResponseTime"] (milliseconds) # stats["eventBreakdown"] (per-event totals) ``` ### verify_webhook_signature (free function) Verify the `X-TurboDocx-Signature` header on an incoming request. Exported directly from `turbodocx_sdk` and does **not** require `TurboWebhooks.configure()` — receivers commonly run in a different process (or different deploy) than the management code. ```python from turbodocx_sdk import verify_webhook_signature ok = verify_webhook_signature( raw_body, # str | bytes — raw bytes as received signature_header, # value of X-TurboDocx-Signature timestamp_header, # value of X-TurboDocx-Timestamp webhook_secret, # the secret from create_webhook tolerance_seconds=300, # default; pass 0 to disable timestamp check (NOT recommended) ) ``` ## Framework Examples ```python # server.py from flask import Flask, request, abort from turbodocx_sdk import TurboWebhooks, verify_webhook_signature TurboWebhooks.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], ) app = Flask(__name__) @app.post("/webhooks/turbodocx") def receive_webhook(): raw_body = request.get_data() # raw bytes — required for HMAC verification ok = verify_webhook_signature( raw_body, request.headers.get("X-TurboDocx-Signature", ""), request.headers.get("X-TurboDocx-Timestamp", ""), os.environ["TURBODOCX_WEBHOOK_SECRET"], ) if not ok: abort(401, "Invalid signature") event = json.loads(raw_body) if event["eventType"] == "signature.document.completed": pass # ... elif event["eventType"] == "signature.document.voided": pass # ... return ("ok", 200) if __name__ == "__main__": app.run(port=3000) ``` ```python # server.py from fastapi import FastAPI, Request, HTTPException from turbodocx_sdk import TurboWebhooks, verify_webhook_signature TurboWebhooks.configure( api_key=os.environ["TURBODOCX_API_KEY"], org_id=os.environ["TURBODOCX_ORG_ID"], ) app = FastAPI() @app.post("/webhooks/turbodocx") async def receive_webhook(request: Request): raw_body = await request.body() # raw bytes — required for HMAC verification ok = verify_webhook_signature( raw_body, request.headers.get("x-turbodocx-signature", ""), request.headers.get("x-turbodocx-timestamp", ""), os.environ["TURBODOCX_WEBHOOK_SECRET"], ) if not ok: raise HTTPException(status_code=401, detail="Invalid signature") event = json.loads(raw_body) if event["eventType"] == "signature.document.completed": pass # ... elif event["eventType"] == "signature.document.voided": pass # ... return {"ok": True} ``` ```python # views.py from django.http import HttpResponse, HttpResponseBadRequest from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from turbodocx_sdk import verify_webhook_signature @csrf_exempt @require_POST def turbodocx_webhook(request): raw_body = request.body # raw bytes — required for HMAC verification ok = verify_webhook_signature( raw_body, request.headers.get("X-TurboDocx-Signature", ""), request.headers.get("X-TurboDocx-Timestamp", ""), os.environ["TURBODOCX_WEBHOOK_SECRET"], ) if not ok: return HttpResponseBadRequest("Invalid signature") event = json.loads(raw_body) # dispatch event["eventType"] ... return HttpResponse("ok") ``` ## Error Handling ```python from turbodocx_sdk import ( TurboDocxError, AuthenticationError, AuthorizationError, ValidationError, ConflictError, NotFoundError, RateLimitError, NetworkError, ) try: await TurboWebhooks.create_webhook(urls=urls, events=events) except ConflictError: # 409 — signature webhook already exists; update or delete it instead pass except ValidationError: # 400 — non-HTTPS URL, empty events array, etc. pass except AuthorizationError: # 403 — API key lacks the administrator role pass except AuthenticationError: # 401 — bad or revoked API key pass except NotFoundError: # 404 — operating on a non-existent webhook pass except RateLimitError: # 429 — back off and retry pass except NetworkError: # request never reached the server (DNS, refused, timeout) pass except TurboDocxError as e: # catch-all for any other typed SDK error (raw 5xx, etc.) print(f"Error {getattr(e, 'status_code', '?')}: {e}") ``` ### Common Error Codes | Status | Class | When | |---|---|---| | 400 | `ValidationError` | Non-HTTPS URL, empty events, invalid body | | 401 | `AuthenticationError` | Missing or invalid API key | | 403 | `AuthorizationError` | Valid key without administrator role | | 404 | `NotFoundError` | Operating on a non-existent webhook | | 409 | `ConflictError` | Creating when the signature webhook already exists | | 429 | `RateLimitError` | Rate limit exceeded — back off | ## Runnable End-to-End Example A complete, validated CRUD walkthrough lives in the SDK repo: **[`packages/py-sdk/examples/turbowebhooks_crud.py`](https://github.com/TurboDocx/SDK/blob/main/packages/py-sdk/examples/turbowebhooks_crud.py)** It exercises every CRUD step plus every error branch (400 / 401 / 403 / 404 / 409) against a live backend. Run with `python examples/turbowebhooks_crud.py` after exporting `TURBODOCX_API_KEY` and `TURBODOCX_ORG_ID`. Override `TURBODOCX_RECEIVER_URL` to point at a real receiver (e.g. webhook.site, ngrok). ## Gotchas - **One webhook per org.** Every method targets the fixed-name `signature` webhook. Creating it twice raises `ConflictError` (409). To manage multiple webhooks per org, call the REST API directly. - **Save the secret immediately.** `create_webhook` and `regenerate_webhook_secret` return the HMAC secret **once**. There is no endpoint to retrieve it later. If you lose it, rotate. - **Use the raw bytes for verification.** The HMAC is over the exact request body received. Never `json.loads(...)` first. In Flask, use `request.get_data()`; in FastAPI, `await request.body()`; in Django, `request.body`. - **`verify_webhook_signature` is a free function**, not a method on `TurboWebhooks` — import it directly from `turbodocx_sdk`. It has no `api_key`/`org_id` dependency. - **All methods are async.** Call them from inside an `async def`, or wrap with `asyncio.run(...)` from a synchronous context (e.g. a sync Flask view). Mixing `asyncio.run` per-request inside a hot path will reinitialize the event loop on every call — prefer FastAPI or an async Flask variant for production receivers that also need to make SDK calls. - **`test_webhook` summary now includes per-URL errors.** Check `result["summary"]["errors"]` to see exactly which receiver failed and why. ## See Also - [TurboSign → Webhooks](/docs/TurboSign/Webhooks) — concepts, dashboard UI, retry behavior - [TurboWebhooks JavaScript / TypeScript SDK](/docs/SDKs/webhooks-javascript) — same API, JS idioms - [TurboWebhooks PHP SDK](/docs/SDKs/webhooks-php) — same API, PHP idioms - [TurboSign Python SDK](/docs/SDKs/python) — sending documents for signature - [SDKs Overview](/docs/SDKs) — all SDKs across all five languages - [turbodocx-sdk on PyPI](https://pypi.org/project/turbodocx-sdk/) - [TurboDocx SDK on GitHub](https://github.com/TurboDocx/SDK/tree/main/packages/py-sdk) --- # Template Generation API Integration This comprehensive guide walks you through the Template Generation API integration process. Learn how to programmatically upload new templates or select existing ones, then generate beautiful documents with variable substitution using our RESTful API. ![Template Generation API Integration Overview](/img/template-generation-api/banner.gif) ## Overview The Template Generation API offers **two flexible paths** to document creation, because we believe in choice (and because forcing everyone down one path would be like making everyone eat vanilla ice cream forever): ### **Path A: Upload New Template** 1. **Upload & Create** - Upload your .docx/.pptx template and extract variables automatically 2. **Generate Document** - Fill variables and create your deliverable ### **Path B: Select Existing Template** 1. **Browse Templates** - Explore your template library with search and filtering 2. **Template Details** - Load template variables and preview PDF 3. **Generate Document** - Fill variables and create your deliverable ![Template Generation API Workflow](/img/template-generation-api/options.png) ### Key Features - **RESTful API**: Standard HTTP methods with JSON and multipart payloads - **Bearer Token Authentication**: Secure API access using JWT tokens - **Dual Entry Points**: Start fresh with uploads OR use existing templates - **Smart Variable Extraction**: Automatic detection of placeholders in uploaded documents - **Rich Variable Types**: Support for text, subvariables, stacks, and AI-powered content - **Template Library**: Full CRUD operations with search, filtering, and organization - **Real-time Processing**: Track document generation status throughout the process ## TLDR; Complete Working Examples 🚀 Don't want to read the novel? Here's the executive summary: ### Available Variable Types | Type | Description | Example Placeholder | Use Case | | --------------- | ----------------------------- | ---------------------- | --------------------------- | | `text` | Simple text replacement | `{CompanyName}` | Basic text substitution | | `subvariables` | Nested variable structures | `{Employee.FirstName}` | Complex hierarchical data | | `variableStack` | Multiple instances of content | `{Projects[0].Name}` | Repeating sections, lists | | `richText` | HTML/formatted text content | `{Description}` | Formatted text with styling | | `aiPrompt` | AI-generated content | `{Summary}` | Dynamic content generation | ### Complete Dual-Path Workflow ### Quick Variable Structure Example Here's what a complex variable payload looks like: ```json { "templateId": "abc123-def456-ghi789", "name": "Employee Contract Draft", "description": "Generated contract for new hire", "variables": [ { "name": "Employee", "placeholder": "{Employee}", "text": "John Smith", "subvariables": [ { "placeholder": "{Employee.Title}", "text": "Senior Developer" }, { "placeholder": "{Employee.StartDate}", "text": "2024-01-15" } ] }, { "name": "Projects", "placeholder": "{Projects}", "variableStack": { "0": { "text": "Project Alpha - Backend Development", "subvariables": [ { "placeholder": "{Projects.Duration}", "text": "6 months" } ] }, "1": { "text": "Project Beta - API Integration", "subvariables": [ { "placeholder": "{Projects.Duration}", "text": "3 months" } ] } } } ] } ``` Now let's dive into the template wizardry... ## Prerequisites Before you begin your journey into template automation nirvana, ensure you have: - **API Access Token**: Bearer token for authentication - **Organization ID**: Your organization identifier - **Template Files**: .docx or .pptx files with placeholder variables (for uploads) ### 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 ![Template API Credentials](/img/turbosign/api/api-key.png) ![Organization ID Location](/img/turbosign/api/org-id.png) ## Authentication All Template Generation API requests require authentication using a Bearer token in the Authorization header: ```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 ``` ## Path A: Upload New Template Start fresh by uploading a new template document. This path is perfect when you've crafted a beautiful new template and want to jump straight into document generation. ### Endpoint ```http POST https://api.turbodocx.com/template/upload-and-create ``` ### 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 (Form Data) ```javascript { "templateFile": [DOCX_OR_PPTX_FILE_BINARY], "name": "Employee Contract Template", "description": "Standard employee contract with variable placeholders", "variables": "[]", // Optional: Pre-defined variables (usually extracted automatically) "tags": "[]" // Optional: Categorization tags } ``` ### Response ```json { "data": { "results": { "template": { "name": "Employee Contract Template", "description": "Standard employee contract with variable placeholders", "fonts": [ { "name": "Arial", "usage": 269 }, { "name": "Calibri", "usage": 45 } ], "defaultFont": "Arial", "orgId": "2d66ecf0-a749-475d-9403-9956d0f67884", "createdBy": "9d829e80-1135-4a97-93ea-cc2a1eecc9da", "createdOn": "2025-09-19T15:45:47.000Z", "updatedOn": "2025-09-19T15:45:47.000Z", "isActive": 1, "id": "31cc4cce-4ed7-4f3b-aa80-0b9a4f995412", "variables": null, "projectspaceId": null, "templateFolderId": null, "metadata": null }, "redirectUrl": "/templates/31cc4cce-4ed7-4f3b-aa80-0b9a4f995412/generate" } } } ``` ### Response Fields | Field | Type | Description | | ---------------------------------------- | ------------ | ----------------------------------------------- | | `data.results.template.id` | string | Unique template identifier (use for generation) | | `data.results.template.name` | string | Template name as provided | | `data.results.template.description` | string | Template description | | `data.results.template.fonts` | array | Array of font objects with name and usage | | `data.results.template.defaultFont` | string | Default font name for the template | | `data.results.template.orgId` | string | Organization ID | | `data.results.template.createdBy` | string | User ID who created the template | | `data.results.template.createdOn` | string | ISO timestamp of template creation | | `data.results.template.updatedOn` | string | ISO timestamp of last template update | | `data.results.template.isActive` | number | Active status (1 = active, 0 = inactive) | | `data.results.template.variables` | array\|null | Auto-extracted variables (null if none found) | | `data.results.template.projectspaceId` | string\|null | Project space ID (null if not assigned) | | `data.results.template.templateFolderId` | string\|null | Folder ID (null if not in folder) | | `data.results.template.metadata` | object\|null | Additional metadata (null if not set) | | `data.results.redirectUrl` | string | Frontend URL to redirect for variable filling | ### Deliverable Response Fields | Field | Type | Description | | ------------------------------------- | ------- | -------------------------------- | | `data.results.deliverable.id` | string | Unique deliverable identifier | | `data.results.deliverable.name` | string | Deliverable name as provided | | `data.results.deliverable.createdBy` | string | Email of the user who created it | | `data.results.deliverable.createdOn` | string | ISO timestamp of creation | | `data.results.deliverable.orgId` | string | Organization ID | | `data.results.deliverable.isActive` | boolean | Whether deliverable is active | | `data.results.deliverable.templateId` | string | Original template ID used | ### Code Examples ## Path B: Select Existing Template Browse your template library to find the perfect starting point. This path lets you leverage templates you've already created and organized. ### Step 1: Browse Templates List all available templates and folders in your organization. #### Endpoint ```http GET https://api.turbodocx.com/template-item ``` #### Headers ```http Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx API Client ``` #### Query Parameters ```http ?limit=25&offset=0&query=contract&showTags=true&selectedTags[]=tag-123 ``` | Parameter | Type | Description | | -------------- | ------- | ----------------------------------- | | `limit` | number | Items per page (default: 6) | | `offset` | number | Pagination offset (default: 0) | | `query` | string | Search term for template names | | `showTags` | boolean | Include tag information in response | | `selectedTags` | array | Filter by specific tag IDs | #### Response ```json { "data": { "results": [ { "id": "0b1099cf-d7b9-41a4-822b-51b68fd4885a", "name": "Employee / Contractor IP Agreement Example", "description": "A Statement of Work template provided by TurboDocx", "createdOn": "2025-05-29T19:07:16.000Z", "updatedOn": "2025-09-08T11:44:11.000Z", "isActive": 1, "type": "template", "createdBy": "9d829e80-1135-4a97-93ea-cc2a1eecc9da", "email": "kahlerasse@gmail.com", "templateFolderId": null, "deliverableCount": 4, "fileSize": 24942, "fileType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "templateCount": 0, "tags": [] }, { "id": "604d1e63-6cdc-4849-9e71-548409bfec69", "name": "Legal", "description": null, "createdOn": "2025-05-29T19:07:16.000Z", "updatedOn": "2025-05-29T19:07:16.000Z", "isActive": 1, "type": "folder", "createdBy": "9d829e80-1135-4a97-93ea-cc2a1eecc9da", "email": "kahlerasse@gmail.com", "templateFolderId": null, "deliverableCount": 0, "fileSize": null, "fileType": null, "templateCount": 0, "tags": [] } ], "totalRecords": 26 } } ``` #### Browse Response Fields | Field | Type | Description | | --------------------------------- | ------------ | ----------------------------------------------- | | `data.results[]` | array | Array of templates and folders | | `data.results[].id` | string | Unique identifier for template or folder | | `data.results[].name` | string | Name of the template or folder | | `data.results[].description` | string\|null | Description (null for some items) | | `data.results[].createdOn` | string | ISO timestamp of creation | | `data.results[].updatedOn` | string | ISO timestamp of last update | | `data.results[].isActive` | number | Active status (1 = active, 0 = inactive) | | `data.results[].type` | string | Item type ("template" or "folder") | | `data.results[].createdBy` | string | User ID who created the item | | `data.results[].email` | string | Email of the creator | | `data.results[].templateFolderId` | string\|null | Parent folder ID (null if at root level) | | `data.results[].deliverableCount` | number | Number of deliverables generated from template | | `data.results[].fileSize` | number\|null | File size in bytes (null for folders) | | `data.results[].fileType` | string\|null | MIME type of template file (null for folders) | | `data.results[].templateCount` | number | Number of templates in folder (0 for templates) | | `data.results[].tags` | array | Array of tag objects | | `data.totalRecords` | number | Total number of items available | ### Step 2: Get Template Details Load specific template information including variables and metadata. #### Endpoint ```http GET https://api.turbodocx.com/template/{templateId} ``` #### Response ```json { "data": { "results": { "id": "0b1099cf-d7b9-41a4-822b-51b68fd4885a", "name": "Employee Contract Template", "description": "Standard employment agreement template", "templateFileType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "templateFolderId": "folder-def456", "variables": [ { "id": "var-123", "name": "Employee Name", "placeholder": "{EmployeeName}", "text": "", "mimeType": "text", "allowRichTextInjection": false, "order": 1, "subvariables": [] }, { "id": "var-456", "name": "Department", "placeholder": "{Department}", "text": "", "mimeType": "text", "allowRichTextInjection": true, "order": 2, "subvariables": [ { "placeholder": "{Department.Manager}", "text": "" } ] } ], "fonts": [ { "name": "Arial", "usage": 269 } ], "defaultFont": "Arial" } } } ``` ### Step 3: Get PDF Preview (Optional) Generate a preview PDF of the template for visual confirmation. #### Endpoint ```http GET https://api.turbodocx.com/template/{templateId}/previewpdflink ``` #### Response ```json { "results": "https://api.turbodocx.com/template/pdf/preview-abc123.pdf" } ``` ### Code Examples ## Final Step: Generate Deliverable Both paths converge here - time to fill those variables and create your masterpiece! This is where the magic happens and placeholders become real content. ### Step 1: Generate Deliverable ### Endpoint ```http POST https://api.turbodocx.com/v1/deliverable ``` ### Headers ```http Content-Type: application/json Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx API Client ``` ### Request Body ```json { "templateId": "0b1099cf-d7b9-41a4-822b-51b68fd4885a", "name": "Employee Contract - John Smith", "description": "Employment contract for new senior developer", "variables": [ { "mimeType": "text", "name": "Employee Name", "placeholder": "{EmployeeName}", "text": "John Smith", "allowRichTextInjection": 0, "autogenerated": false, "count": 1, "order": 1, "subvariables": [ { "placeholder": "{Employee.Title}", "text": "Senior Software Developer" }, { "placeholder": "{Employee.StartDate}", "text": "January 15, 2024" } ], "aiPrompt": "Generate a professional job description for a senior developer role" }, { "mimeType": "text", "name": "Department", "placeholder": "{Department}", "text": "Engineering", "allowRichTextInjection": 1, "autogenerated": false, "count": 3, "order": 2, "subvariables": [ { "placeholder": "{Department.Manager}", "text": "Sarah Johnson" } ], "variableStack": { "0": { "text": "Frontend Development Team", "subvariables": [ { "placeholder": "{Team.Focus}", "text": "React and TypeScript development" } ] }, "1": { "text": "Backend Development Team", "subvariables": [ { "placeholder": "{Team.Focus}", "text": "Node.js and database optimization" } ] } }, "metadata": { "customField": "Engineering Department" }, "aiPrompt": "Describe the key responsibilities of the engineering department" } ], "tags": ["hr-template", "contract", "full-time"], "metadata": { "sessions": [ { "id": "session-abc123", "starttime": "2024-01-15T14:12:10.721Z", "endtime": "2024-01-15T14:13:45.724Z" } ] } } ``` ### Response ```json { "data": { "results": { "deliverable": { "id": "39697685-ca00-43b8-92b8-7722544c574f", "name": "Employee Contract - John Smith", "description": "Employment contract for new senior developer", "createdBy": "api-user@company.com", "createdOn": "2024-12-19T21:22:10.000Z", "orgId": "your-organization-id", "isActive": true, "templateId": "0b1099cf-d7b9-41a4-822b-51b68fd4885a" } } } } ``` ### Variable Structure Deep Dive Understanding the variable structure is key to successful document generation: #### Basic Variable ```json { "name": "Company Name", "placeholder": "{CompanyName}", "text": "Acme Corporation", "mimeType": "text", "allowRichTextInjection": false, "order": 1 } ``` #### Variable with Subvariables ```json { "name": "Employee", "placeholder": "{Employee}", "text": "John Smith", "subvariables": [ { "placeholder": "{Employee.Title}", "text": "Senior Developer" }, { "placeholder": "{Employee.Email}", "text": "john.smith@company.com" } ] } ``` #### Variable Stack (Repeating Content) ```json { "name": "Projects", "placeholder": "{Projects}", "variableStack": { "0": { "text": "Project Alpha", "subvariables": [ { "placeholder": "{Projects.Status}", "text": "In Progress" } ] }, "1": { "text": "Project Beta", "subvariables": [ { "placeholder": "{Projects.Status}", "text": "Completed" } ] } } } ``` ### Request Fields | Field | Type | Required | Description | | ------------------------------------ | ------- | -------- | ---------------------------------------------- | | `templateId` | string | Yes | Template ID from upload or selection | | `name` | string | Yes | Name for the generated deliverable | | `description` | string | No | Description of the deliverable | | `variables[]` | array | Yes | Array of variable definitions and values | | `variables[].name` | string | Yes | Variable display name | | `variables[].placeholder` | string | Yes | Placeholder text in template (e.g., `{Name}`) | | `variables[].text` | string | Yes | Actual value to replace placeholder | | `variables[].mimeType` | string | Yes | Content type ("text", "html", etc.) | | `variables[].allowRichTextInjection` | number | No | Allow HTML/rich text (0 or 1) | | `variables[].subvariables` | array | No | Nested variables within this variable | | `variables[].variableStack` | object | No | Multiple instances for repeating content | | `variables[].aiPrompt` | string | No | AI prompt for content generation | | `variables[].metadata` | object | No | Custom metadata for the variable | | `tags` | array | No | Tags for categorization | | `metadata` | object | No | Additional metadata (sessions, tracking, etc.) | ### Step 2: Download Generated File After generating a deliverable, you'll need to download the actual file. #### Endpoint ```http GET https://api.turbodocx.com/v1/deliverable/file/{deliverableId} ``` #### Headers ```http Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx API Client ``` #### Example Request ```bash curl -X GET "https://api.turbodocx.com/v1/deliverable/file/39697685-ca00-43b8-92b8-7722544c574f" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "x-rapiddocx-org-id: YOUR_ORGANIZATION_ID" \ -H "User-Agent: TurboDocx API Client" \ --output "employee-contract-john-smith.docx" ``` #### Response Returns the binary content of the generated document with appropriate content-type headers: ```http Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document Content-Disposition: attachment; filename="employee-contract-john-smith.docx" Content-Length: 287456 ``` ### Code Examples ## Best Practices ### Security - **Never expose API tokens**: Store tokens securely in environment variables - **Use HTTPS only**: All API calls must use HTTPS in production - **Validate file uploads**: Check file types and sizes before upload - **Sanitize variables**: Validate variable content to prevent injection attacks ### Error Handling - **Check HTTP status codes**: Always verify response status before processing - **Handle file upload failures**: Implement retry logic for large file uploads - **Validate template variables**: Ensure all required variables are provided - **Log API responses**: Keep detailed logs for debugging and monitoring ### Performance - **Optimize file uploads**: Compress .docx/.pptx files when possible - **Cache template details**: Store frequently used template information - **Batch variable processing**: Group related variables together - **Async processing**: Use webhooks for long-running document generation ### Template Preparation - **Use clear placeholders**: Name variables descriptively (e.g., `{CompanyName}` not `{CN}`) - **Consistent formatting**: Use consistent placeholder formats throughout templates - **Test variable extraction**: Verify automatic variable detection works correctly - **Document structure**: Organize templates logically with folders and tags ### Variable Management - **Hierarchical organization**: Use subvariables for related data - **Stack for repetition**: Use variableStack for lists and repeating sections - **Rich text sparingly**: Only enable rich text injection when formatting is needed - **AI prompts**: Provide clear, specific prompts for AI-generated content ## Error Handling & Troubleshooting ### Common HTTP Status Codes | Status Code | Description | Solution | | ----------- | --------------------- | --------------------------------------------- | | `200` | Success | Request completed successfully | | `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 template ID and endpoint URLs | | `413` | Payload Too Large | Reduce file size or compress template | | `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 #### Template Upload Failures **Symptoms**: Upload returns error or times out **Solutions**: - Verify file is .docx or .pptx format - Check file size is under the maximum limit (typically 25MB) - Ensure file is not corrupted or password-protected - Verify network connection and try again #### Variable Extraction Issues **Symptoms**: Variables not detected automatically **Solutions**: - Use consistent placeholder format: `{VariableName}` - Avoid special characters in placeholder names - Ensure placeholders are in main document content (not headers/footers) - Check that placeholders are properly formatted text (not images) #### Template Selection Problems **Symptoms**: Templates not appearing in browse results **Solutions**: - Verify organization ID matches your account - Check that templates are active and not archived - Use correct API endpoint for browsing vs. folder contents - Verify search parameters and filters #### Document Generation Failures **Symptoms**: Deliverable generation fails or produces incorrect output **Solutions**: - Validate all required variables have values - Check variable names match template placeholders exactly - Ensure subvariable structure matches template expectations - Verify file permissions and storage availability #### Font and Formatting Issues **Symptoms**: Generated documents have incorrect fonts or formatting **Solutions**: - Check that rich text injection is enabled only when needed - Validate HTML content in rich text variables ### Debugging Tips 1. **Test with simple templates**: Start with basic templates before adding complexity 2. **Validate JSON payloads**: Use JSON validators before sending requests 3. **Check file encoding**: Ensure .docx/.pptx files are not corrupted 4. **Monitor API quotas**: Track usage to avoid rate limiting 5. **Use development endpoints**: Test with development environment first ## Next Steps ### Advanced Features to Explore Now that you've mastered the basics, consider exploring these advanced capabilities: 📖 **[AI-Powered Content Generation →](/docs/TurboDocx%20Templating/ai-variable-generation)** 📖 **[Webhook Integration for Status Updates →](/docs/Webhooks/webhook-configuration)** 📖 **[Bulk Document Generation →](/docs/Templates/bulk-generation)** 📖 **[Template Version Management →](/docs/Templates/version-control)** ### Related Documentation - [Template Management Guide](/docs/Templates/template-management) - [Variable Types and Formatting](/docs/API/Deliverable%20API#variable-object-structure) - [API Authentication](/docs/API/turbodocx-api-documentation) - [Integration Examples](/docs/Integrations) ## Support Need help with your template integration? We've got you covered: - **Discord Community**: [Join our Discord server](https://discord.gg/NYKwz4BcpX) for real-time support and template wizardry discussions - **Documentation**: [https://docs.turbodocx.com](https://docs.turbodocx.com) - **Template Gallery**: Browse example templates for inspiration --- Ready to become a template automation wizard? Choose your path (upload new or select existing) and start generating beautiful documents programmatically! 🎯 --- ## Additional Information # Guides, Tips and Tricks Here you will find additional standalone guides to features, uses and tips. ## Variable Entries There are multiple ways to enter and edit your variables. In this guide we will break down the different ways in three parts: Simple Text, Advanced, Variable Stack and Disable Variable. ### Simple Text Entries In this first example we will be using simple text entries. Things such as "CustomerName" or "ProjectName" already have formating in the template so just clicking on the box and typing your entry will be sufficient for us here. ![Simple text variable entry fields for CustomerName and ProjectName](/img/how_to_create_a_deliverable/step_6.png) ### Advanced Entries What happens if we need a more robust entry? Maybe we want something from our Knowledgebase entries, an image or just add some formatting to the text. We'll go through each option in this section. 1. First, what we will do is click the more options or the triple dot button located next to the variable boxes. Here you will see all the options we just discussed and more. ![more options](/img/how_to_create_a_deliverable/step_10.png) 2. Next, please select the option you would like to utilize. #### Lookup To select something from your Knowledgebase, click Lookup. A popup window will display all your entries. Scroll or search through them. The search feature will look for the keyword in the name, tags, or content. Larger entries have a preview option. Once found, click the blue "Select" button. ![lookup](/img/how_to_create_a_deliverable/step_20.png) :::tip - This is where tags and descriptions when creating the Knowledgebase entry may come in handy. - Even after selection, you can edit variables for any necessary changes from the Knowledgebase entry. Simply click the content box for simple text changes. For other variable types, use the pencil and paper icon to edit. ::: #### Image For images, click Image. A pop up will come up where you can click on it to open your file browser and choose an image. ![Image](/img/how_to_create_a_deliverable/step_img.PNG) #### Rich Text For text that you need to format, or if you'd like to add a table you will click on Rich Text. A new window will pop-up as shown in the image below. Here you will see familiar options such as text formats, colors as well as the ability to add tables among other things. Just click the "Save" button when you're done ![rich text](/img/how_to_create_a_deliverable/step_24.png) #### Date For a date, click on Date. A calendar will pop-up above the variable which you can then use to select the date. ![date](/img/how_to_create_a_deliverable/date.png) ### Variable Stack and Disable Variable Sometimes you will need more than one entry for a given variable and others you will not need a certain variable at all. These two options will allow you to do that. #### Variable Stack If you need multiple variable entries or images for a section of your Deliverable, click Create Variable Stack under more options. You'll return to the original page with a "+" button under the variable. Add stacks by clicking "+", to remove them click "-". ![rich text](/img/how_to_create_a_deliverable/step_vs.PNG) #### Disable Variable Sometimes a variable is helpful to have in a template but may not be needed in every Deliverable. For instances where the variable is not needed you can click on more options and then Disable Variable. This will remove the requirement to have content in that variable. To undo this just click the button that now replaced the more options button. ![rich text](/img/how_to_create_a_deliverable/step_10.png) ## Subvariables In your Knowledgebase you may have entries that you will want to have variables in. For example a entry outlining each parties responsibites with `{CustomerName}` or `{OrganizationName}` as variables. We call these Subvariables. Here we will go over how you can utilize them. ### Adding Subvariables 1. We will start by creating a new Knowledgebase entry (see How to Create a Knowledgebase Entry if you need a refresher), and we will be selecting "Rich Text" from our more options menu. 2. Much like adding a variable in a Template you will fill out your entry utilizing \{brackets\} around the subvariable that you would like to create, in this example we will be using \{CustomerName\} in part of our chart. 3. As you can see once the subvariable is created a seperate input box will appear on the left side under `{CustomerName}`. Whatever you input into this box will be placed in the subvariable! ![img](/img/additional_information/subvariables.png) :::tip When you are a creating a deliverable and choose an entry with a subvariable click on the pencil and paper icon to see the same window in step 3 and enter your subvariable input. ![Subvariable input field shown when editing a knowledgebase entry in a deliverable](/img/additional_information/subvdeliv.png) ::: ## AI Generation Do you have notes on your CRM, or a website that can help you fill out some of the variables when creating a Deliverable? Our Ai Generation will allow you to input data and find the information it needs to fill out variables in your Deliverable! This guide will show you how. ### Navigating to AI Generation 1. Select a Template and create a Deliverable (see How to Create a Deliverable if you need a refresher). 2. Click on the "Fill in With Notes + AI" ![Fill in With Notes plus AI button on the deliverable creation page](/img/additional_information/aibutton.png) ### Utilizing the AI Generator #### Landing Page After you've navigated to the "Fill in With Notes + AI" page it should look like the image below. As you can see there are areas of input for Websites and other things on the left, an area to input notes you may have on the right (after you click on Advanced Settings) and your variables below. ![AI Generation landing page with website input, notes area, and variable fields](/img/additional_information/ai1.png) #### Input and Generate 1. For this example we will be using the a fictional company's website and notes from our CRM. Once we have added our content hit the "Generate" button. ![AI Generation page with website URL and CRM notes entered, ready to generate](/img/additional_information/aiinput.png) 2. Our AI scans through notes and websites to identify anything matching your template variables. It populates this information in the "Value" column, which you can review and approve by checking the corresponding boxes on the right. ![AI-generated variable values with approval checkboxes for review](/img/additional_information/ai2.png) 3. Click the "Save" button and you will be brought back to the original "Create Deliverable" screen where you can continue to fill out your variables and create your Deliverable. ## Variable Troubleshooting Here are some best practices when troubleshooting why your variable is not showing up in the Template. ### Video Tutorial 1. All variables will need to be one word, so do not put any spaces in the middle of the `{brackets}`. 2. If the variable will be an image or rich text, make sure the variable is on it's own line. Use the show paragraph tool if you need help. The location for that tool in Word and Google Docs are in the image below. ![Paragraph marker tool location in Word and Google Docs toolbars](/img/how_to_create_a_template/paragraphtool.png) When you activate the tool, it will resemble the image below. Since `{CustomerName}` is plain text, it can be incorporated into a paragraph. However, we will be utilizing a chart in Rich Text for `{Scope}`, so it needs to be on its own line. Paragraph symbols will indicate this distinction clearly. ![Paragraph markers showing inline text variable vs. section variable on its own line](/img/additional_information/ptoolexample.png) --- # Brand Identity ## Why Brand Identity Matters Your brand is one of your most valuable business assets. Whether you're a law firm, consulting agency, or enterprise organization, consistent branding across all client-facing documents: - **Builds trust and professionalism** - clients immediately recognize your materials - **Saves time and reduces errors** - eliminates manual formatting and brand guideline violations - **Ensures compliance** - automatically enforces corporate brand standards across all teams - **Increases efficiency** - setup once, apply everywhere automatically TurboDocx's Brand Identity feature allows you to configure organization-wide branding settings that apply consistently across all your document templates. Unlike template-specific font embedding, Brand Identity creates tenant-wide standards for colors, typography, and styling that ensure brand consistency across all generated documents. ## Prerequisites Checklist Before you begin, ensure you have: - [ ] **Organization administrator access** to your TurboDocx account - [ ] **Your organization logo** (PNG or JPG format, 200x200px or larger recommended) - [ ] **Brand color codes** (hex codes if you have them, or we'll extract from your logo) - [ ] **A few minutes** for initial setup :::tip Ready to Start? If you just want to get up and running quickly, jump to the [Quick Start](#quick-start) section below. You can always come back to customize further. ::: ## Quick Start **Goal**: Get your brand colors and basic styling applied across all documents quickly. ### Step 1: Access Brand Settings 1. Go to **Organization Settings** → **Formatting Settings** 2. You'll see the Brand Identity interface with upload, controls, and preview areas ### Step 2: Upload Your Logo ![Logo Upload Interface](/img/brandidentity/LogoUpload.png) 1. Click **Upload Logo** and select your company logo 2. Click **Save Changes** - TurboDocx will automatically extract your brand colors 3. You should see extracted colors appear in the Brand Colors section ### Step 3: Apply Quick Typography ![Quick Setup Fonts](/img/brandidentity/QuickSetupFonts.png) 1. In the **Quick Setup** section, choose a preset size: - **Small**: Conservative, formal documents - **Normal**: Balanced for most business use - **Large**: Bold, presentation-style 2. Preview panel updates with your new heading sizes ### 🎉 You're Done! Your brand identity is now active across all new documents. Existing templates will update to use your brand settings automatically. **Immediate value**: All new documents will now use your brand colors and consistent typography without any additional setup. --- ## Overview Brand Identity configuration includes: - **Logo Upload**: Upload your organization's logo for automatic color extraction - **Brand Colors**: Use extracted colors or customize manually - **Typography**: Configure heading and body text sizes and colors - **Table Styling**: Customize table backgrounds, headers, and borders - **Real-time Preview**: See changes instantly as you configure :::info Key Difference **Brand Identity** sets organization-wide styling standards, while **[Working with Fonts](./Working%20with%20Fonts.md)** covers embedding specific desktop fonts in individual templates. ::: ## Detailed Configuration (Optional) *Already completed the Quick Start? Skip to [What's Next](#whats-next) or continue here for advanced customization.* ### Choose Your Configuration Approach **Option A: Automatic Setup** *(Recommended for most users)* - Upload logo → Let TurboDocx extract colors → Use presets - **Best for**: Quick deployment, consistent results **Option B: Manual Customization** *(For brand-specific requirements)* - Custom color selection → Granular typography control → Advanced table styling - **Best for**: Strict brand guidelines, unique styling ### Accessing Brand Identity Settings 1. Navigate to your organization settings 2. Select **Formatting Settings** from the configuration menu 3. You'll see the Brand Identity configuration interface with three main areas: - Logo upload section (top) - Configuration controls (left side) - Live preview panel (right side) You can see the upload area and preview panel side-by-side ### Logo Upload & Save ![Logo Upload Interface](/img/brandidentity/LogoUpload.png) 1. **Upload Your Logo** - Click the **Upload Logo** button at the top of the page - Select your logo file (recommended formats: PNG, JPG) - Optimal size: 200x200px or larger for best results - Logo preview appears in upload area 2. **Save Changes** - After uploading, click **Save Changes** to apply the logo - TurboDocx will automatically analyze your logo to extract brand colors - The extraction process may take a few moments - Brand colors appear automatically in the Brand Colors section below :::tip Logo Tips - Use high-resolution logos for better color extraction - PNG files with transparent backgrounds work best - Square or horizontal logos typically work better than vertical ones ::: ## Brand Colors ![Brand Colors and Preview Panel](/img/brandidentity/BrandColorsandPreview.png) ### Automatic Color Extraction Once you upload your logo, TurboDocx automatically extracts your brand's primary colors: - **Primary Color**: The dominant color from your logo - **Secondary Color**: The most prominent accent color - **Additional Colors**: Supporting colors found in your logo The extracted colors appear in the **Brand Colors** section on the left-hand side of the interface. ### Manual Color Override **When to customize**: Your extracted colors don't match your brand guidelines exactly, or you need specific hex codes. You can customize any extracted color: 1. **Override Extracted Colors** - Click on any color swatch in the Brand Colors section - Use the color picker to select a new color - Enter specific hex codes for precise color matching - Colors update in real-time in the preview panel 2. **Color Guidelines** - Ensure sufficient contrast between text and background colors - Test colors across different document types in the preview - Text remains clearly readable in preview panel ### Using the Preview Panel The **Preview Panel** on the right-hand side shows: - **Real-time Updates**: Changes appear instantly as you modify colors - **Document Samples**: See how colors look in actual document layouts - **Different Elements**: Preview headings, body text, tables, and other components Preview panel shows your colors applied to sample document content ## Typography & Font Configuration ### Quick Setup Options For rapid configuration, use the **Quick Setup** presets: 1. **Preset Sizes** for Headings 1–3: - **Small**: Conservative sizing for formal documents (H1: 16pt, H2: 14pt, H3: 12pt) - **Normal**: Balanced sizing for most use cases (H1: 18pt, H2: 16pt, H3: 14pt) - **Large**: Bold sizing for impactful presentations (H1: 22pt, H2: 18pt, H3: 16pt) 2. **Apply Presets**: - Select your preferred preset - Changes apply immediately to H1, H2, and H3 headings in preview panel - You can still make granular adjustments afterward **Stop here if**: You're satisfied with the preset sizes and want to keep setup simple. ### Granular Typography Adjustments For precise control, expand the typography accordions: #### Headings (H1, H2, H3) ![Heading Configuration Controls](/img/brandidentity/HeadingConfig.png) **For each heading level, configure:** - **Font Size**: Adjust size in points or pixels - **Font Color**: Choose from brand colors or custom colors **Best Practices:** - Maintain clear hierarchy: H1 > H2 > H3 in size - Use consistent color schemes across heading levels - Ensure sufficient contrast for accessibility #### Body Text Configuration **Configure body text settings:** - **Font Size**: Optimal reading size (typically 11-12pt for documents) - **Font Color**: Usually darker colors for readability ## Table Styling Configuration ![Table Styling Configuration](/img/brandidentity/TableSetup.png) Customize how tables appear in your documents: ### Table Background Colors - **Main Table Background**: Overall table background color - **Alternating Rows**: Optional striped pattern for better readability - **Color Intensity**: Adjust opacity for subtle backgrounds ### Header & Subheader Styling **Table Headers:** - **Header Background Color**: Primary header row styling - **Header Text Color**: Ensure contrast with background - **Subheader Background Color**: Secondary header styling for complex tables **Styling Tips:** - Use brand colors for headers to maintain consistency - Ensure text remains readable on colored backgrounds - Consider print-friendly colors for documents that may be printed ### Border & Text Styling **Border Configuration:** - **Table Border Color**: Outline color for the entire table - **Cell Border Color**: Internal grid line colors - **Border Width**: Thin, medium, or thick border options **Text Colors (Optional):** - **Data Text Color**: Color for table cell content - **Override Body Text**: Use different colors in tables vs. regular text - **Emphasis Colors**: Highlight important data points ### Table Preview The preview panel shows your table styling applied to sample data, allowing you to: - Verify color combinations work well together - Check readability across different cell types - Ensure consistent branding appearance ## Advanced Configuration Tips ### Color Consistency - **Use Brand Color Palette**: Stick to extracted or manually set brand colors - **Create Color Hierarchy**: Primary colors for headers, secondary for accents ### Typography Hierarchy - **Establish Clear Levels**: Make H1-H3 distinctly different sizes - **Maintain Proportions**: Use mathematical ratios (1.25x, 1.5x, 2x) between levels - **Consider Context**: Adjust sizes based on document types (reports vs. presentations) ### Template Integration Your Brand Identity settings automatically apply to: - **New Templates**: All newly created templates inherit brand settings - **Existing Templates**: Update existing templates to use brand settings - **Generated Documents**: All output maintains brand consistency ## What's Next? Now that your Brand Identity is configured, here's how to put it to work: ### Immediate Next Steps 1. **Test with existing templates** - Generate a document from an existing template to see your branding applied 2. **Create your first branded template** - Follow our [How to Create a Template](./How%20to%20Create%20a%20Template.md) guide 3. **Set up team access** - Ensure team members have appropriate permissions to use templates ### Building Your Document Workflow - **Templates**: Your brand settings automatically apply to all new and existing templates - **Deliverables**: Every generated document will use your brand identity consistently - **Knowledge Base**: Combine with [knowledge base entries](./How%20to%20Create%20a%20Knowledgebase%20Entry.md) for fully automated, branded documents ### Integration Opportunities - **Salesforce Integration**: Branded proposals generated directly from CRM data - **Teams/Zoom Integration**: Consistent meeting follow-ups and reports - **Document Library**: Build a library of branded templates for different use cases :::tip Pro Tip Start with 2-3 core templates (proposal, report, letter) to see immediate value, then expand your template library as needed. ::: ## Troubleshooting ### Problem: Logo Won't Upload **Is your file under 5MB?** - ❌ **No**: Compress your logo file or use a smaller version - ✅ **Yes**: Continue to format check **Is your file PNG or JPG format?** - ❌ **No**: Convert your logo to PNG or JPG format - ✅ **Yes**: Check image resolution (should be 200x200px or larger) ### Problem: Colors Aren't Extracting Well **Does your logo have multiple distinct colors?** - ❌ **No (monochrome/single color)**: Use manual color override to add your brand colors - ✅ **Yes**: Continue to contrast check **Are colors in your logo high contrast?** - ❌ **No (subtle/low contrast)**: Use manual color override for better results - ✅ **Yes**: Try a higher resolution logo or manual override specific colors ### Problem: Changes Not Appearing **Are changes showing in the preview panel?** - ❌ **No**: Refresh the page and try again - ✅ **Yes**: Changes may take a few minutes to apply to generated documents **Did you click "Save Changes"?** - ❌ **No**: Click Save Changes to apply your brand settings - ✅ **Yes**: Generate a test document to verify settings are applied ### Template Application Issues **Brand Settings Not Applying:** - Verify templates are set to use organization brand settings - Check template-specific overrides that may conflict - Ensure templates are saved after brand updates **Font Conflicts:** - TurboDocx by default uses the font found in the template - For custom fonts, use [Working with Fonts](./Working%20with%20Fonts.md) embedding ## Getting Help If you encounter issues with Brand Identity configuration: 1. **Check Template Compatibility**: Ensure templates support brand identity features 2. **Verify Permissions**: Confirm you have organization admin privileges 3. **Test with Sample Documents**: Generate test documents to verify settings 4. **Contact Support**: Provide specific details about configuration issues and document types For technical font embedding questions, refer to [Working with Fonts](./Working%20with%20Fonts.md). :::info Next Steps After configuring your Brand Identity, create new templates or update existing ones to see your branding applied consistently across all generated documents. ::: --- ## How to Create a Deliverable Now that you have a Template and entries in your Knowledgebase, let's put it all together and create your first document or what we call a Deliverable. ## Step 1: Getting to and Selecting your Deliverable Let's get you to your Templates and select the template for your first Deliverable. 1. On the left hand side click on the Templates tab. 2. Next select the template you want to use. If you have folders set up or pages of templates that makes it hard to find the template you want don't forget the search feature! ![This is the image for A with the text: Login and then clicked](/img/how_to_create_a_deliverable/step_1.png) 3. With your template open it should look like the image below. Here you can check to make sure all your variables are present and also have a preview of the template to make sure you have the right document. If everything looks correct, you can go ahead and click "Create Deliverable ![This is the image for SPAN with the text: TurboDocx Demo and then clicked](/img/how_to_create_a_deliverable/step_2.png) ## Step 2: Creating your Deliverable After you've clicked "Create Deliverable" you will be brought to a new page where you will be able to enter and edit all your variables. There are multiple ways to enter and edit your variables so for Step 2 we will break it down into 3 parts: Simple Text Entries, Advanced Entries, Variable Stack and Disable Variable. For future reference this information can also be found under the "Additional Information" tab under "Variable Entry". ### Simple Text Entries In this first example we will be using simple text entries. Things such as "CustomerName" or "ProjectName" already have formating in the template so just clicking on the box and typing your entry will be sufficient for us here. ![Simple text variable entry fields for CustomerName and ProjectName](/img/how_to_create_a_deliverable/step_6.png) ### Advanced Entries What happens if we need a more robust entry? Maybe we want something from our Knowledgebase entries, an image or just add some formatting to the text. We'll go through each option in this section. 1. First, what we will do is click the more options or the triple dot button located next to the variable boxes. Here you will see all the options we just discussed and more. ![more options](/img/how_to_create_a_deliverable/step_10.png) 2. Next, please select the option you would like to utilize. #### Lookup To select something from your Knowledgebase, click Lookup. A popup window will display all your entries. Scroll or search through them. The search feature will look for the keyword in the name, tags, or content. Larger entries have a preview option. Once found, click the blue "Select" button. ![lookup](/img/how_to_create_a_deliverable/step_20.png) :::tip - This is where tags and descriptions when creating the Knowledgebase entry may come in handy. - Even after selection, you can edit variables for any necessary changes from the Knowledgebase entry. Simply click the content box for simple text changes. For other variable types, use the pencil and paper icon to edit. ::: #### Image For images, click Image. A pop up will come up where you can click on it to open your file browser and choose an image. ![Image](/img/how_to_create_a_deliverable/step_img.PNG) #### Rich Text For text that you need to format, or if you'd like to add a table you will click on Rich Text. A new window will pop-up as shown in the image below. Here you will see familiar options such as text formats, colors as well as the ability to add tables among other things. Just click the "Save" button when you're done ![rich text](/img/how_to_create_a_deliverable/step_24.png) #### Date For a date, click on Date. A calendar will pop-up above the variable which you can then use to select the date. ![date](/img/how_to_create_a_deliverable/date.png) ### Variable Stack and Disable Variable Sometimes you will need more than one entry for a given variable and others you will not need a certain variable at all. These two options will allow you to do that. #### Variable Stack If you need multiple variable entries or images for a section of your Deliverable, click Create Variable Stack under more options. You'll return to the original page with a "+" button under the variable. Add stacks by clicking "+", to remove them click "-". ![rich text](/img/how_to_create_a_deliverable/step_vs.PNG) #### Disable Variable Sometimes a variable is helpful to have in a template but may not be needed in every Deliverable. For instances where the variable is not needed you can click on more options and then Disable Variable. This will remove the requirement to have content in that variable. To undo this just click the button that now replaced the more options button. ![rich text](/img/how_to_create_a_deliverable/step_10.png) ## Step 3: Generate Deliverable Once you've completed filling out the variables in your Deliverable, click the "Generate Deliverable" button on the bottom right of the page and then "Confirm" on the pop up. ![This is the image for BUTTON with the text: Generate Deliverable and then clicked](/img/how_to_create_a_deliverable/step_29.png) ## Step 4: Export Deliverable Once you've clicked "Confirm" the next screen will be a preview of your deliverable. After you've taken a look you can press the "Export" button on the top right. Select how you would like to export it and you're done! ![This is the image for BUTTON with the text: Confirm and then clicked](/img/how_to_create_a_deliverable/step_35.png) ## Finished Congratulations on creating your first Deliverable. I hope these tutorials were helpful and we look forward to working with you to create quick and easy documentation automation! --- ## How to Create Document Templates # Create Document Templates for Automated Proposals, Contracts & Reports Transform data from meetings, CRM systems, and integrations into professional documents automatically. This guide shows you how to create document templates for proposals, statements of work, quotes, and contracts that can be populated with content from your connected data sources. **Perfect for creating:** - 📄 **Proposals** from discovery calls, opportunities, and project data - 📋 **Statements of Work (SOW)** based on requirement discussions and business data - 💰 **Quotes & Estimates** using meeting notes, specifications, and pricing information - 📝 **Project Contracts** incorporating terms from calls and account details - 🤝 **Service Agreements** with details from consultations and contact systems - 📊 **Reports & Summaries** with action items, metrics, and data from your integrations Transform conversations and CRM data into professional documents automatically - streamlining your workflow with intelligent document generation. Congratulations on starting your first document template. In this guide, we will show you how to create your first document template and how to upload it to your TurboDocx account. ## Video Tutorial ## Step 1: Create New Document Template Let's start with creating our first document template. 1. Open up whatever application you will be using to create your document (Word, Google Drive, etc). 2. Anything you would like to be a variable or an item that will change from document to document will be put between these `{brackets}`. This can be single items such as `{Date}` or `{CustomerName}` or it can be whole sections such as `{Scope}` as seen in the images below. ![Word document template with bracket variables like CustomerName and Date](/img/how_to_create_a_template/CreatingATemplateDoc1Title.PNG) ![Second page of template document showing Scope section variable](/img/how_to_create_a_template/CreatingATemplateDoc2Title.PNG) 3. Once you've finished your document with the variables that you want please save it and move on to the next step. :::tip - If you've previously created a document you want to use as a template, go ahead and use that. Just remember to replace all the variables within the `{brackets}`. Find and replace may be helpful here. - All variables will need to be one word, so do not put any spaces in the middle of the `{brackets}`. - Remember your Knowledgebase, if the variable has the same name as something in your Knowledgebase TurboDocx will use the info from the Knowledgebase to fill in the corresponding Variable. - If you want to include an image or rich text, make sure the variable is on it's own line. Use the show paragraph tool if you need help. The location for that tool in Word and Google Docs are in the image below. ![Paragraph marker tool location in Word and Google Docs toolbars](/img/how_to_create_a_template/paragraphtool.png) ::: ## Step 2: Upload Template Document Now that we've created our first template, let's upload it to TurboDocx. 1. Login to your TurboDocx account you should land on the Templates tab if not it can be found on the left side. Then, On the top right of your window, click on the "New Template" button. ![TurboDocx Templates tab with New Template button highlighted](/img/how_to_create_a_template/newtemp.png) 2. As you can see, there are multiple ways to upload a Document. For this example, we will click on the "Upload Template" area then choose the document you just saved in the last step or drag and drop your that document into the area. ![Template upload dialog with drag-and-drop area and upload options](/img/how_to_create_a_template/step_1.png) ## Step 3: Preview Template On the next screen, you will see a preview of your template. 1. Check to make sure all the variables you wanted are listed under the variables column. - (Optional) - On this page, you can also create default entries for your template that you can change later when you are creating the deliverable. ![Template preview screen showing detected variables and Create Template button](/img/how_to_create_a_template/step_3.png) 2. Once you've checked to make sure all your variable are set, go ahead and click the "Create Template" button on the bottom right corner (you may need to scroll) :::tip - Tags can be very useful to help find certain templates, and Knowledgebase entries. Don't forget to utilize them! You can also always come back and add them in the future. - If your variables are not showing up, go back to step one and check if you're using the right brackets, and that there are no spaces in your variable name. ::: ## Finished Congratulations on uploading your first document template. You should be able to see it on the template tab once you go back to the main page. --- ## How to Create a Knowledgebase Entry Congratulations on starting your first entry in the Knowledgebase. In this guide, we will show you how to create your first entry. ## Step 1: Navigate to and Create a Knowledgebase Entry Let's start by getting to the Knowledgebase and creating a new entry. 1. On the left side of the screen click on the tab "Knowledgebases". ![Knowledgebases tab in TurboDocx left navigation sidebar](/img/how_to_create_knowledgebases/step_1.png) 2. Next, click the blue "New" button on the top left of the page. ![New button on the Knowledgebases page to create an entry](/img/how_to_create_knowledgebases/step_2a.png) ## Step 2: Enter the Information After clicking "New", a new window should pop-up on the right side of your screen. Here is where you will input all of the relevant information. 1. First, name your entry. We would suggest something that will help to indicate to other users what the Knowledgebase entry will include. 2. Next, create a placeholder. Don't worry the brackets are automatically used for you. 3. In the "Default Value" box, enter the info you want to use for this Knowledgebase entry. For plain text, type directly into the box. If you need formatting, images, or dates, click the "More Options" button for those choices. - For additional info on how to use these tools, see the "Additional Information" tab under "Variable Entry". - You can also add subvariables in your entries. For more information on how, see the "Additional Information" tab under "Subvariables" ![Knowledgebase entry form with name, placeholder, and default value fields](/img/how_to_create_knowledgebases/step_8.png) 4. Lastly, press the "Submit" button on the bottom right of the page. :::tip - Although "Tags" and "Add Description" are optional, this can help others find and understand the purpose of the entry quickly and easily. - If the "Placeholder" is the same as a variable used in the Template TurboDocx will automatically place the Knowledgebases entry in that variable slot. Of course, you will still be able to change it. ::: ## Step 3: Search Once you've clicked the "Submit" button you should go back to the main Knowledgebases screen. From here you can add new entries, delete outdated or uneeded entries, manage your tags, or search for a specific entry. Note: When searching TurboDocx will look for the keyword in the name, tags and content! ![Knowledgebases main screen with search bar, tag management, and entry list](/img/how_to_create_knowledgebases/step_2b.png) ## Finished Congratulations on creating your first Knowledgebases entry. --- ## How to Create Presentation Templates # Create Presentation Templates for Data-Driven Presentations Turn your business data into professional presentations automatically. This guide shows you how to create presentation templates that can be populated with data from meetings, CRM systems, project management tools, and other integrations. **Ideal for:** - 📞 **Post-meeting summaries** from calls and conferences - 🎯 **Client presentations** based on opportunity data and discovery sessions - 📋 **Project status decks** from planning meetings and project systems - 📊 **Sales presentations** customized with prospect and opportunity information - 🔄 **Recurring reporting formats** with dynamic content from your integrations - 📈 **Executive dashboards** with real-time metrics and KPIs Congratulations on starting your first presentation template. In this guide, we will show you how to create your first presentation template and how to upload it to your TurboDocx account. ## Video Tutorial ## Step 1: Create New Presentation Template Let's start with creating our first presentation template. 1. Open PowerPoint or your preferred presentation application. 2. Create your slide content, leaving areas where you want dynamic content. 3. For each variable area, you'll need to create invisible shapes to hold your variables. Screenshots Coming Soon ## Step 2: Setting Up Variables in Slides For slide deck templates, follow these specific steps to ensure variables work correctly: 1. **Delete existing content** from the slide where you want dynamic content 2. **Insert a shape:** - Go to **Insert → Shape** in the ribbon - Choose a rectangle - Draw the shape where you want the dynamic content to go 3. **Add your variable:** - Click inside the shape - Type your variable name (e.g., `{WhatWeHeard}`) - Remember: all one word, no spaces 4. **Align text to top:** - With the shape selected, go to **Shape Format → Align Text → Top** - This keeps everything uniform across slides 5. **Make the shape invisible:** - Set **Shape Fill** to "No Fill" - Set **Shape Outline** to "No Fill" - This removes the colored box, making it invisible 6. **Format the text:** - Change the font color to black (or your preferred color) - This makes it easy and clear to see in your final deck Screenshots Coming Soon ### Example Slide Setup For a slide with sections like: - What we heard - How we can help - Follow-up questions Create separate invisible shapes for each section with variables like: - `{WhatWeHeard}` - `{HowWeCanHelp}` - `{FollowUpQuestions}` Each variable should be in its own invisible rectangle shape, not mixed with other text. ## Step 3: Upload Presentation Template Now that we've created our first presentation template, let's upload it to TurboDocx. 1. Login to your TurboDocx account you should land on the Templates tab if not it can be found on the left side. Then, On the top right of your window, click on the "New Template" button. 2. As you can see, there are multiple ways to upload a Document. For this example, we will click on the "Upload Template" area then choose the presentation you just saved in the last step or drag and drop your presentation into the area. Screenshots Coming Soon ## Step 4: Preview Template On the next screen, you will see a preview of your template. 1. Check to make sure all the variables you wanted are listed under the variables column. - (Optional) - On this page, you can also create default entries for your template that you can change later when you are creating the deliverable. 2. Once you've checked to make sure all your variable are set, go ahead and click the "Create Template" button on the bottom right corner (you may need to scroll) Screenshots Coming Soon :::tip - Tags can be very useful to help find certain templates, and Knowledgebase entries. Don't forget to utilize them! You can also always come back and add them in the future. - If your variables are not showing up, make sure you're using invisible rectangle shapes and that there are no spaces in your variable name. ::: ## Finished Congratulations on uploading your first presentation template. You should be able to see it on the template tab once you go back to the main page. --- ## How to Create a Template Congratulations on starting your first template. In this guide, we will show you how to create your first template and how to upload it to your TurboDocx account. ## Video Tutorial ## Step 1: Create New Template Let's start with creating our first template. 1. Open up whatever application you will be using to create your document (Word, Google Drive, etc). 2. Anything you would like to be a variable or an item that will change from document to document will be put between these `{brackets}`. This can be single items such as `{Date}` or `{CustomerName}` or it can be whole sections such as `{Scope}` as seen in the images below. ![Word document template with bracket variables like CustomerName and Date](/img/how_to_create_a_template/CreatingATemplateDoc1Title.PNG) ![Second page of template document showing Scope section variable](/img/how_to_create_a_template/CreatingATemplateDoc2Title.PNG) 3. Once you've finished your document with the variables that you want please save it and move on to the next step. :::tip - If you've previously created a document you want to use as a template, go ahead and use that. Just remember to replace all the variables within the `{brackets}`. Find and replace may be helpful here. - All variables will need to be one word, so do not put any spaces in the middle of the `{brackets}`. - Remember your Knowledgebase, if the variable has the same name as something in your Knowledgebase TurboDocx will use the info from the Knowledgebase to fill in the corresponding Variable. - If you want to include an image or rich text, make sure the variable is on it's own line. Use the show paragraph tool if you need help. The location for that tool in Word and Google Docs are in the image below. ![Paragraph marker tool location in Word and Google Docs toolbars](/img/how_to_create_a_template/paragraphtool.png) ::: ## Step 2: Upload Template Document Now that we've created our first template, let's upload it to TurboDocx. 1. Login to your TurboDocx account you should land on the Templates tab if not it can be found on the left side. Then, On the top right of your window, click on the "New Template" button. ![TurboDocx Templates tab with New Template button highlighted](/img/how_to_create_a_template/newtemp.png) 2. As you can see, there are multiple ways to upload a Document. For this example, we will click on the "Upload Template" area then choose the document you just saved in the last step or drag and drop your that document into the area. ![Template upload dialog with drag-and-drop area and upload options](/img/how_to_create_a_template/step_1.png) ## Step 3: Preview Template On the next screen, you will see a preview of your template. 1. Check to make sure all the variables you wanted are listed under the variables column. - (Optional) - On this page, you can also create default entries for your template that you can change later when you are creating the deliverable. ![Template preview screen showing detected variables and Create Template button](/img/how_to_create_a_template/step_3.png) 2. Once you've checked to make sure all your variable are set, go ahead and click the "Create Template" button on the bottom right corner (you may need to scroll) :::tip - Tags can be very useful to help find certain templates, and Knowledgebase entries. Don't forget to utilize them! You can also always come back and add them in the future. - If your variables are not showing up, go back to step one and check if you're using the right brackets, and that there are no spaces in your variable name. ::: ### Example Slide Setup For a slide with sections like: - What we heard - How we can help - Follow-up questions Create separate invisible shapes for each section with variables like: - `{WhatWeHeard}` - `{HowWeCanHelp}` - `{FollowUpQuestions}` Each variable should be in its own invisible rectangle shape, not mixed with other text. --> ## Finished Congratulations on uploading your first template. You should be able to see it on the template tab once you go back to the main page. --- # Office Integration Edit your TurboDocx templates and deliverables directly in Microsoft Word and PowerPoint with full Office functionality, while keeping everything synchronized and preventing editing conflicts. ## What You Can Do With Office Integration, you can: - **Open templates in Word** - Edit with all Word features (track changes, comments, formatting, etc.) - **Open presentations in PowerPoint** - Use PowerPoint's full editing capabilities - **Save changes automatically** - Your edits sync back to TurboDocx in real-time - **Work safely** - File locking prevents others from editing while you work - **See updates instantly** - PDF previews regenerate automatically after changes ## Supported Applications - **Microsoft Word** - For `.docx` template files - **Microsoft PowerPoint** - For `.pptx` presentation files :::info Office Installation Required You need Microsoft Office installed on your computer. The web versions of Office don't support this integration. ::: ## How to Use Office Integration ### Opening a Document 1. **Find your template or deliverable** in TurboDocx 2. **Click "Open in Word"** or **"Open in PowerPoint"** button 3. **Wait for Office to launch** - This may take a few seconds 4. **Start editing** - The document opens with all your content ready to edit :::tip First Time Setup Your browser may ask permission to open Office applications. Click "Allow" or "Open" to enable the integration. ::: ### While Editing **What you'll see:** - Your document opens in Word/PowerPoint as normal - An overlay appears in your browser showing "Editing in Progress" - The document is locked - other users can't edit it while you work **What you can do:** - Edit the document with full Office features - Use track changes, comments, and formatting tools - Save your work frequently (Ctrl+S or Cmd+S) - Work offline - changes sync when you're back online **What happens automatically:** - Your changes save back to TurboDocx when you press Ctrl+S - Template variables are detected and updated - PDF previews regenerate in the background - Other users see the document is being edited ### Finishing Your Edits When you're done editing: 1. **Save your final changes** in Word/PowerPoint (Ctrl+S) 2. **Return to your browser** with TurboDocx open 3. **Click "I'm Done Editing"** on the overlay 4. **The document becomes available** for other users :::warning Important: What Happens After "I'm Done Editing" Once you click "I'm Done Editing", **your Office document becomes read-only**. If you try to save again in Word/PowerPoint, you'll get an error message. This is normal and prevents conflicts with other users. **If you need to make more changes:** - Click "Open in Word" again to start a new editing session - This will give you a fresh editing session with save permissions ::: ## What Other Users See ### When Someone is Editing - **Lock indicator** shows who is currently editing - **"Being edited by [Name]"** message appears - **All editing functions are disabled** for other users - **PDF preview updates** as the editor saves changes ### When Editing is Complete - **Lock is automatically removed** - **All users can edit again** - **Updated PDF preview** reflects the latest changes ## Common Workflows ### Quick Edit Workflow Perfect for small changes: 1. Click "Open in Word" 2. Make your changes 3. Save (Ctrl+S) 4. Click "I'm Done Editing" ### Extended Editing Session For major revisions: 1. Click "Open in Word" 2. Make multiple rounds of changes 3. Save frequently throughout your session 4. Take breaks - your session stays active 5. When completely finished, click "I'm Done Editing" ### Collaboration Workflow Working with team members: 1. **Person A** opens document for editing 2. **Person B** sees lock message and waits 3. **Person A** completes edits and clicks "I'm Done Editing" 4. **Person B** can now click "Open in Word" to make their changes ### Template Variable Updates When you add or modify template variables: 1. Edit your template in Word 2. Save your changes (Ctrl+S) 3. Return to browser - you'll see "Files are changed" button 4. Click "Files are changed" to update variables 5. Fill in any new template variables 6. Click "I'm Done Editing" when finished ## Troubleshooting ### "Office Won't Open" Issues **Problem:** Clicking "Open in Word" doesn't open the document **Solutions:** 1. **Check Office Installation** - Make sure Microsoft Office is installed and activated 2. **Try a Different Browser** - Edge works best, Chrome is also good 3. **Allow Pop-ups** - Your browser might be blocking the Office protocol 4. **Restart Your Browser** - Close all tabs and restart 5. **Clear Cache** - Clear your browser cache and cookies ### "Can't Save" Issues **Problem:** Getting errors when trying to save in Word/PowerPoint **Most Common Cause:** You clicked "I'm Done Editing" but Office is still open **Solution:** This is normal behavior! Once you click "I'm Done Editing", Word becomes read-only to prevent conflicts. **To continue editing:** 1. Go back to TurboDocx in your browser 2. Click "Open in Word" again 3. This starts a new editing session with save permissions **Other Save Issues:** - **Check your internet connection** - Saves need an internet connection - **Make sure no one else is editing** - Only one person can edit at a time - **Try saving again** - Sometimes network delays cause temporary save failures ### "Document is Locked" Messages **Problem:** TurboDocx says the document is being edited by someone else **What this means:** Another user currently has the document open for editing **Solutions:** 1. **Wait for them to finish** - They'll click "I'm Done Editing" when finished 2. **Contact the person** - Ask them if they're still actively editing 3. **Admin can force unlock** - If you're an admin and the person isn't responding ### "Long Delay" When Opening Word **Problem:** Word takes 10+ seconds to open after making template changes **Why this happens:** TurboDocx is processing your template variable changes **Solutions:** - **Be patient** - This is normal after variable updates - **Don't click multiple times** - Wait for Word to open - **Check your internet speed** - Slow connections take longer ## Tips for Best Results ### Save Frequently - Press **Ctrl+S** (Windows) or **Cmd+S** (Mac) often - Don't rely on auto-save - manual saves sync faster - Save before making major changes ### Use "I'm Done Editing" Properly - **Always click it** when you're completely finished - **Don't click it** if you plan to continue editing soon - **Remember** - Office becomes read-only after clicking ### Collaboration Etiquette - **Communicate with your team** about who's editing what - **Don't keep documents open** unnecessarily - **Finish editing sessions promptly** so others can work ### Working Offline - You can work offline - changes sync when you reconnect - Save frequently when you're back online - Avoid having multiple people edit the same document offline ## System Requirements ### Computer Requirements - **Windows 10/11** or **macOS** (latest versions recommended) - **Microsoft Office installed** (Office 365, Office 2019, or newer) - **Stable internet connection** for syncing changes ### Browser Requirements - **Chrome** (recommended) - **Microsoft Edge** (recommended) - **Firefox** (may need setup) - **Safari** (limited support) ### Office Versions - ✅ **Office 365** - Full support - ✅ **Office 2021** - Full support - ✅ **Office 2019** - Full support - ❌ **Office Online** - Not supported (web versions) - ❌ **Older versions** - May not work properly ## Getting Help If you're still having issues: 1. **Contact your team admin** - They can check locks and permissions 2. **Try a different computer** - Helps identify computer-specific issues 3. **Test with a different template** - Helps identify template-specific issues 4. **Check with IT support** - They can verify Office installation and network settings Remember: Office Integration is designed to work seamlessly, but like any technology, sometimes you need to try a few solutions to get everything working perfectly! --- # Template Troubleshooting Before you throw your computer, watch this video! Your variables aren't broken, but your template might need a little bit of tender loving care. Here are the most common issues and how to fix them. ## Video Tutorial ## Common Variable Issues ### 1. Variable Naming Rules All variables must be **one word only** - no spaces allowed inside the `{brackets}`. ✅ Correct - One word only {'{CustomerName}'} {'{ProjectDate}'} {'{ScopeSection}'} ✨ These variable names will work perfectly in your templates ❌ Incorrect - Contains spaces {'{Customer Name}'} {'{Project Date}'} {'{Scope Section}'} ⚠️ Spaces inside brackets will prevent variables from being recognized :::tip Naming Best Practices - Use **CamelCase** for multi-word variables: `{CustomerName}` instead of `{Customer Name}` - Keep names **descriptive but concise**: `{ProjectStartDate}` instead of `{Date}` - Be **consistent** across your templates: always use the same format ::: ### 2. Variables Must Be On Their Own Line **Critical Rule:** When inserting sections, images, or rich text, the variable needs to be on its own line. Don't squeeze it into a sentence. #### Examples: How to Format Variables Correctly ❌ Incorrect - Variable mixed with other text Rich Text/Section Variables: Proposal Section: {'{ProposalSection}'}, If you have any questions about the proposal... Image Variables: Photo: {'{HeadshotImage}'}, Nicolas Fry, CEO ⚠️ This format will prevent variables from working properly ✅ Correct - Variable on its own line Rich Text/Section Variables: Proposal Section: {'{ProposalSection}'} If you have any questions about the proposal... Image Variables: Photo: {'{HeadshotImage}'} Nicolas Fry, CEO ✨ This format ensures variables work correctly and content flows properly :::tip Key Takeaway Notice how in the correct examples, each variable is **completely isolated** on its own line with blank lines above and below. This separation is crucial for rich text, images, and section variables to function properly. ::: ### 3. Use Paragraph Markers to Debug One of the most common mistakes is thinking a variable is on its own line when it's not. To be sure, turn on the paragraph marker tool: **In Microsoft Word:** - Click the paragraph icon (¶) to reveal hidden spaces and line breaks - It's the fastest way to catch formatting issues before they break your template **In Google Docs:** - Go to **View → Show non-printing characters** - You'll get the same results as Word ![Paragraph marker tool location in Word and Google Docs toolbars](/img/how_to_create_a_template/paragraphtool.png) When you activate the tool, it will resemble the image below. Since `{CustomerName}` is plain text, it can be incorporated into a paragraph. However, we will be utilizing a chart in Rich Text for `{Scope}`, so it needs to be on its own line. Paragraph symbols will indicate this distinction clearly. ![Paragraph markers showing inline text variable vs. section variable on its own line](/img/additional_information/ptoolexample.png) ## Quick Troubleshooting Steps When your variables aren't working, follow these steps in order: ### 1. Check Variable Naming No spaces inside brackets - use `{CustomerName}` not `{Customer Name}` ### 2. Verify Line Placement Variables for images, rich text, or sections must be on their own line ### 3. Use Paragraph Markers Turn on ¶ symbols to see hidden formatting issues ### 4. For Presentation Templates Use invisible rectangle shapes, not text boxes → [See presentation setup guide](./How%20to%20Create%20a%20Presentation%20Template) ### 5. Test Your Template Create a simple deliverable to verify everything works → [Learn how to create deliverables](./How%20to%20Create%20a%20Deliverable) :::tip Advanced Troubleshooting & Best Practices **If variables still aren't working:** - **Start simple:** Test with basic text variables first, then add complex ones - **Double-check spacing:** Use paragraph markers to confirm variables are completely isolated - **For presentations:** Ensure shapes are truly invisible (no fill, no outline) **Best practices for success:** - **Test your template** by creating a deliverable before finalizing → [See how](./How%20to%20Create%20a%20Deliverable) - **Keep variable names descriptive** but concise - **Use consistent formatting** across all templates → [Document templates](./How%20to%20Create%20a%20Document%20Template) | [Presentation templates](./How%20to%20Create%20a%20Presentation%20Template) ::: ## Need More Help? Still stuck? We're here to help! Check out our comprehensive guides: - [📄 Document Templates](./How%20to%20Create%20a%20Document%20Template) - Learn to create Word/Google Doc templates - [📊 Presentation Templates](./How%20to%20Create%20a%20Presentation%20Template) - Learn to create PowerPoint templates - [🎯 Create Deliverables](./How%20to%20Create%20a%20Deliverable) - Learn to generate documents from templates - [📚 Full Documentation](https://docs.turbodocx.com) - Complete TurboDocx documentation If you need additional help, don't hesitate to reach out to our support team. --- # Working with Fonts TurboDocx supports a wide variety of fonts for document generation. This guide covers the fonts available in TurboDocx, how to use them effectively, and how to embed custom fonts in your templates. ## Embedding Fonts in Templates For documents that require specific fonts not included in TurboDocx's standard collection, you can embed fonts directly in your DOCX or PPTX templates. ### For Microsoft Word (DOCX Templates) #### Windows 1. Open your template in Microsoft Word 2. Go to **File → Options ![This is the image for clicking file -> options in Microsoft Word.](/img/embedding_fonts/FileOptions.png) 3. Go to the **Save** tab 4. Under "Preserve fidelity when sharing this document," check: - ✅ **Embed fonts in the file** - ✅ **Embed only the characters used in the document** (recommended for smaller file sizes) - ✅ **Do not embed common system fonts** (optional but recommended) ![This is the image for clicking file -> options in microsoft Word.](/img/embedding_fonts/SaveandEmbedFontsInFile.png) 5. Click **OK** 6. Save your document 7. Upload the updated template to TurboDocx #### macOS 1. Open your template in Microsoft Word 2. Go to **Word → Preferences → Save** 3. Under "Preserve fidelity when sharing this document," check: - ✅ **Embed fonts in the file** - ✅ **Embed only the characters used in the document** (recommended) 4. Click **OK** 5. Save your document 6. Upload the updated template to TurboDocx ### For Microsoft PowerPoint (PPTX Templates) #### Windows 1. Open your template in Microsoft PowerPoint 2. Go to **File → Options** ![This is the image for clicking file -> options in Microsoft PowerPoint.](/img/embedding_fonts/pptxFileOptions.png) 3. Go to the **Save** Tab 4. Under "Preserve fidelity when sharing this presentation," check: - ✅ **Embed fonts in the file** - ✅ **Embed only the characters used in the presentation** ![This is the image for clicking file -> options in Microsoft PowerPoint.](/img/embedding_fonts/pptxSaveandEmbed.png) 5. Click **OK** 6. Save your presentation 7. Upload the updated template to TurboDocx #### macOS 1. Open your template in Microsoft PowerPoint 2. Go to **PowerPoint → Preferences → Save** 3. Under "Preserve fidelity when sharing this presentation," check: - ✅ **Embed fonts in the file** - ✅ **Embed only the characters used in the presentation** 4. Click **OK** 5. Save your presentation 6. Upload the updated template to TurboDocx :::tip Pro Tip Embedding only characters used in the document significantly reduces file size while ensuring your custom fonts display correctly. ::: ## Font Support in TurboDocx ### PDF Generation (TurboSign) TurboDocx's PDF generator includes many common fonts for high-quality document rendering: #### Sans-Serif Fonts - **Poppins** - Modern geometric font, ideal for headings and contemporary designs - **Lato** - Professional and neutral, excellent for body text and business documents - **Inter** - Screen-optimized font, perfect for digital documents and small text - **Roboto** - Clean and readable, Google's system font - **Open Sans** - Friendly and approachable, widely used in web and print #### Serif Fonts - **Merriweather** - Elegant and readable, designed for long-form reading - **Roboto Slab** - Modern slab serif that pairs well with sans-serif fonts - **Playfair Display** - Stylish display font for headers and decorative text - **Tinos** - Times New Roman alternative, perfect for traditional documents #### Monospace Fonts - **Courier Prime** - Clean monospace font for code snippets and technical content ### System Fonts TurboDocx also supports standard system fonts including: - Arial, Helvetica (substituted with Roboto) - Times New Roman (substituted with Tinos) - Georgia (substituted with Merriweather) - Calibri (substituted with Carlito) - Cambria (substituted with Caladea) :::info Font Substitution TurboDocx automatically substitutes proprietary fonts with high-quality open-source alternatives to ensure consistent rendering across all platforms if font's are not embedded within your Document Template. ::: ## Troubleshooting Font Issues ### Common Problems and Solutions **Font not displaying correctly:** 1. Ensure the font is properly embedded in your template 2. Check that you've uploaded the updated template with embedded fonts 3. Verify the font name matches exactly (including spaces and capitalization) **File size too large:** - Enable "Embed only characters used in document" option - Consider using TurboDocx's built-in fonts instead - Remove unnecessary font variants (bold, italic) if not used **Font substitution occurring:** - Check that the font is properly embedded - Ensure the font license allows embedding - Verify template was saved after enabling font embedding ### Getting Help If you encounter font-related issues: 1. Check that your template uses supported fonts 2. Verify font embedding settings are correctly configured 3. Test with TurboDocx's built-in fonts as alternatives 4. Contact support with specific details about the font and document type :::info Microsoft Support For detailed instructions on font embedding in Microsoft Office, refer to [Microsoft's official documentation](https://support.microsoft.com/en-us/office/benefits-of-embedding-custom-fonts-cb3982aa-ea76-4323-b008-86670f222dbc). ::: --- # AI-Powered Variable Generation API Transform your document workflows with intelligent, context-aware variable generation. This API leverages advanced AI to automatically create rich, relevant content for your template variables by analyzing uploaded files, understanding context, and generating human-quality text based on your specific prompts. ## Overview The AI Variable Generation API represents the cutting edge of document automation, enabling you to: - **Generate Intelligent Content**: Create contextually relevant variable content using AI - **Process File Attachments**: Extract insights from Excel, Word, PDF, and other document formats - **Context-Aware Generation**: Leverage template context for more accurate content creation - **Rich Text Support**: Generate formatted content with HTML, markdown, or plain text - **Smart Data Extraction**: Automatically parse and understand structured data from spreadsheets ### Key Capabilities 🧠 **AI-Powered Content Creation**: Advanced language models generate human-quality content 📎 **File Attachment Processing**: Upload and analyze documents for context-driven generation 📊 **Spreadsheet Intelligence**: Select specific sheets and extract relevant data automatically 🎯 **Context Integration**: Use existing templates to inform and guide content generation ✨ **Rich Text Generation**: Create formatted content with styling and structure 🔧 **Customizable Prompts**: Fine-tune AI behavior with specific instructions and hints ## How It Works The AI Variable Generation process follows a simple but powerful workflow: 1. **Upload Context Files** - Attach documents (Excel, Word, PDF) that contain relevant data 2. **Define Variable Parameters** - Specify the variable name, placeholder, and generation context 3. **Craft AI Prompts** - Provide specific instructions to guide content generation 4. **Generate Content** - AI analyzes files and context to create intelligent variable content 5. **Integrate Results** - Use generated content directly in your template workflows ## TLDR; Quick Example 🚀 Ready to jump in? Here's a complete working example: ```bash curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-123e4567-e89b-12d3-a456-426614174000=@financial-report.xlsx' \ -F 'fileResourceMetadata={"123e4567-e89b-12d3-a456-426614174000":{"selectedSheet":"Q4 Results","hasMultipleSheets":true}}' \ -F 'name=Company Performance Summary' \ -F 'placeholder={Q4Performance}' \ -F 'templateId=template-abc123' \ -F 'aiHint=Generate a professional executive summary of Q4 financial performance based on the attached spreadsheet data' \ -F 'richTextEnabled=true' ``` **Response:** ```json { "data": { "mimeType": "html", "text": "Q4 Performance Summary: Our organization achieved exceptional results in Q4 2024, with revenue growing 23% year-over-year to $4.2M. Key highlights include improved operational efficiency, successful product launches, and strong market penetration in target segments." } } ``` Now let's dive into the complete implementation guide... ## Prerequisites Before you begin generating AI-powered variables, ensure you have: - **API Access Token**: Bearer token for authentication - **Organization ID**: Your organization identifier - **Template Context** (Optional): Existing template ID for enhanced context - **Source Files** (Optional): Documents containing data for AI analysis ### 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 ### Supported File Types The AI Variable Generation API supports a wide range of file formats: | File Type | Extensions | Use Cases | | ----------------- | ----------------------- | ----------------------------------------------- | | **Spreadsheets** | `.xlsx`, `.xls`, `.csv` | Financial data, reports, lists, structured data | | **Documents** | `.docx`, `.doc`, `.pdf` | Contracts, reports, proposals, text content | | **Presentations** | `.pptx`, `.ppt` | Slide content, presentations, visual data | | **Images** | `.png`, `.jpg`, `.jpeg` | Charts, diagrams, visual content analysis | | **Text Files** | `.txt`, `.md` | Plain text, documentation, notes | ## Authentication All AI Variable Generation API requests require authentication using a Bearer token: ```http Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx AI Client ``` ## API Reference ### Generate Single Variable Create AI-powered content for a single template variable with optional file attachments. #### Endpoint ```http POST https://api.turbodocx.com/ai/generate/variable/one ``` #### Headers ```http Content-Type: multipart/form-data Authorization: Bearer YOUR_API_TOKEN x-rapiddocx-org-id: YOUR_ORGANIZATION_ID User-Agent: TurboDocx AI Client ``` #### Request Body (Form Data) The request uses multipart form data to support file uploads alongside variable parameters: ```javascript { // File attachment (optional) "FileResource-{uuid}": [BINARY_FILE_DATA], // File metadata (required if file attached) "fileResourceMetadata": "{\"file-uuid\":{\"selectedSheet\":\"Sheet1\",\"hasMultipleSheets\":true}}", // Variable definition "name": "Company Performance Summary", "placeholder": "{PerformanceSummary}", // Context and guidance "templateId": "template-abc123", // Optional: for context "aiHint": "Generate a professional summary of company performance", // Output settings "richTextEnabled": "true" // or "false" } ``` #### Request Parameters | Parameter | Type | Required | Description | | ---------------------- | ------ | ----------- | ---------------------------------------------------- | | `FileResource-{uuid}` | file | No | Binary file data for AI analysis | | `fileResourceMetadata` | string | Conditional | JSON metadata for attached files | | `name` | string | Yes | Display name for the variable | | `placeholder` | string | Yes | Template placeholder (e.g., `{VariableName}`) | | `templateId` | string | No | Template ID for context-aware generation | | `aiHint` | string | Yes | Instructions for AI content generation | | `richTextEnabled` | string | No | Enable HTML/rich text output (`"true"` or `"false"`) | #### File Metadata Structure When attaching files, provide metadata to guide AI processing: ```json { "file-uuid-here": { "selectedSheet": "Q4 Results", // For spreadsheets: specific sheet "hasMultipleSheets": true, // Whether file has multiple sheets "dataRange": "A1:D100", // Optional: specific cell range "contentType": "financial-data" // Optional: content classification } } ``` #### Response ```json { "data": { "mimeType": "text|html|markdown", "text": "Generated variable content based on AI analysis" } } ``` #### Response Fields | Field | Type | Description | | --------------- | ------ | ------------------------------------------- | | `data.mimeType` | string | Content format (`text`, `html`, `markdown`) | | `data.text` | string | Generated variable content | ## Advanced Features ### File Attachment Workflows #### Excel/Spreadsheet Processing When working with spreadsheets, the AI can analyze specific sheets and data ranges: ```bash # Example: Financial data analysis curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-fin123=@quarterly-financials.xlsx' \ -F 'fileResourceMetadata={"fin123":{"selectedSheet":"Income Statement","hasMultipleSheets":true,"dataRange":"A1:F50"}}' \ -F 'name=Revenue Analysis' \ -F 'placeholder={RevenueAnalysis}' \ -F 'aiHint=Analyze the quarterly revenue trends and provide insights on growth patterns, highlighting key metrics and year-over-year changes' \ -F 'richTextEnabled=true' ``` #### Document Content Extraction For text documents, the AI can extract and synthesize key information: ```bash # Example: Contract analysis curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-doc456=@contract-draft.docx' \ -F 'fileResourceMetadata={"doc456":{"contentType":"legal-document"}}' \ -F 'name=Contract Key Terms' \ -F 'placeholder={KeyTerms}' \ -F 'aiHint=Extract and summarize the key terms, obligations, and important dates from this contract document' \ -F 'richTextEnabled=false' ``` ### Context-Aware Generation Leverage existing template context for more accurate content generation: ```bash # Using template context curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'name=Project Scope Description' \ -F 'placeholder={ProjectScope}' \ -F 'templateId=project-template-789' \ -F 'aiHint=Generate a detailed project scope description that aligns with the project template structure and includes deliverables, timeline, and success criteria' \ -F 'richTextEnabled=true' ``` ### Rich Text Generation Enable rich text for formatted output with HTML styling: ```bash # Rich text example curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'name=Executive Summary' \ -F 'placeholder={ExecutiveSummary}' \ -F 'aiHint=Create a comprehensive executive summary with bullet points, key metrics, and strategic recommendations formatted for presentation' \ -F 'richTextEnabled=true' ``` **Rich Text Response Example:** ```json { "data": { "mimeType": "html", "text": "Executive SummaryOverview: Q4 2024 delivered exceptional results across all key performance indicators.Revenue Growth: 23% increase year-over-yearMarket Expansion: Successfully entered 3 new geographic marketsOperational Efficiency: 15% improvement in cost optimizationStrategic Recommendations: Continue aggressive growth strategy while maintaining operational excellence." } } ``` ## Code Examples ### Complete Implementation Examples ## Use Cases & Examples ### 1. Financial Report Analysis **Scenario**: Generate executive summaries from quarterly financial spreadsheets ```bash curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-12345=@Q4-financials.xlsx' \ -F 'fileResourceMetadata={"12345":{"selectedSheet":"Summary","hasMultipleSheets":true}}' \ -F 'name=Financial Performance Summary' \ -F 'placeholder={FinancialSummary}' \ -F 'aiHint=Create a concise executive summary highlighting revenue growth, profit margins, and key financial metrics from the Q4 data' \ -F 'richTextEnabled=true' ``` ### 2. Contract Key Terms Extraction **Scenario**: Extract important terms and dates from legal documents ```bash curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-67890=@service-agreement.pdf' \ -F 'name=Contract Terms' \ -F 'placeholder={ContractTerms}' \ -F 'aiHint=Extract contract duration, payment terms, key obligations, and important deadlines in a structured format' \ -F 'richTextEnabled=false' ``` ### 3. Project Proposal Generation **Scenario**: Create project descriptions based on scope documents ```bash curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-abcde=@project-requirements.docx' \ -F 'name=Project Description' \ -F 'placeholder={ProjectDescription}' \ -F 'templateId=proposal-template-123' \ -F 'aiHint=Generate a professional project description including objectives, deliverables, timeline, and success criteria based on the requirements document' \ -F 'richTextEnabled=true' ``` ### 4. Data-Driven Insights **Scenario**: Generate insights from research data and surveys ```bash curl 'https://api.turbodocx.com/ai/generate/variable/one' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'x-rapiddocx-org-id: YOUR_ORG_ID' \ -H 'Content-Type: multipart/form-data' \ -F 'FileResource-xyz789=@market-research.xlsx' \ -F 'fileResourceMetadata={"xyz789":{"selectedSheet":"Survey Results","hasMultipleSheets":true}}' \ -F 'name=Market Insights' \ -F 'placeholder={MarketInsights}' \ -F 'aiHint=Analyze the survey data and generate key market insights, trends, and actionable recommendations for product strategy' \ -F 'richTextEnabled=true' ``` ## AI Prompt Engineering ### Writing Effective AI Hints The quality of generated content depends heavily on your AI prompts. Follow these best practices: #### ✅ Do: Be Specific and Clear ```bash # Good prompt 'aiHint=Generate a professional project timeline with 5 key milestones, including dates, deliverables, and success criteria for a 6-month software development project' # Poor prompt 'aiHint=make a timeline' ``` #### ✅ Do: Provide Context and Format Requirements ```bash # Good prompt 'aiHint=Create an executive summary in bullet point format highlighting Q4 revenue (target: $2M), customer acquisition metrics, and year-over-year growth percentages' # Poor prompt 'aiHint=summarize the data' ``` #### ✅ Do: Specify Tone and Audience ```bash # Good prompt 'aiHint=Write a formal, executive-level summary suitable for board presentation, focusing on strategic implications and ROI metrics' # Poor prompt 'aiHint=write a summary' ``` ### Advanced Prompt Techniques #### 1. Role-Based Prompts ```bash 'aiHint=Acting as a senior financial analyst, review the quarterly data and provide insights on revenue trends, cost optimization opportunities, and market positioning recommendations' ``` #### 2. Structured Output Prompts ```bash 'aiHint=Generate a risk assessment with three sections: 1) High-priority risks with mitigation strategies, 2) Medium-priority risks with monitoring plans, 3) Risk summary and overall assessment' ``` #### 3. Data-Driven Prompts ```bash 'aiHint=Based on the attached sales data, calculate month-over-month growth rates, identify top-performing products, and recommend strategies for underperforming segments' ``` ## Best Practices ### File Preparation #### Excel/Spreadsheet Files - **Clean Data**: Remove empty rows, merged cells, and formatting inconsistencies - **Clear Headers**: Use descriptive column headers in the first row - **Consistent Formatting**: Use consistent date formats, number formats, and text casing - **Specific Sheets**: Select the most relevant sheet containing the target data #### Document Files - **Clear Structure**: Use headings, bullet points, and logical organization - **Relevant Content**: Include only content relevant to the AI task - **Text Format**: Ensure text is selectable (not embedded images) - **File Size**: Keep files under 25MB for optimal processing ### Performance Optimization #### Efficient File Usage ```bash # Good: Specific sheet selection 'fileResourceMetadata={"uuid":{"selectedSheet":"Revenue Data","dataRange":"A1:E100"}}' # Poor: Processing entire workbook 'fileResourceMetadata={"uuid":{"hasMultipleSheets":true}}' ``` #### Smart Prompting ```bash # Good: Specific, actionable prompt 'aiHint=Extract the top 5 revenue-generating products from the sales data and provide a brief analysis of their performance trends' # Poor: Vague prompt 'aiHint=tell me about the sales' ``` ### Error Handling #### File Processing Errors - **Check File Format**: Ensure files are in supported formats - **Verify File Size**: Keep attachments under the size limit - **Test File Access**: Ensure files are not corrupted or password-protected #### AI Generation Errors - **Simplify Prompts**: Break complex requests into smaller, specific tasks - **Provide Context**: Include relevant background information in prompts - **Iterate and Refine**: Test prompts and refine based on output quality ### Security Considerations #### Data Privacy - **Sensitive Information**: Review files for confidential data before upload - **Access Controls**: Ensure proper API token management and access restrictions - **Data Retention**: Understand how uploaded files are processed and stored #### API Security - **Token Protection**: Store API tokens securely in environment variables - **HTTPS Only**: Always use HTTPS for API communication - **Rate Limiting**: Implement appropriate rate limiting for production use ## Error Handling & Troubleshooting ### Common HTTP Status Codes | Status Code | Description | Solution | | ----------- | --------------------- | ----------------------------------------------------------- | | `200` | Success | Request completed successfully | | `400` | Bad Request | Check request format, file attachments, and required fields | | `401` | Unauthorized | Verify API token and authentication headers | | `403` | Forbidden | Check organization ID and API permissions | | `413` | Payload Too Large | Reduce file size or compress attachments | | `422` | Unprocessable Entity | Validate AI prompt, file metadata, and parameters | | `429` | Too Many Requests | Implement rate limiting and retry logic | | `500` | Internal Server Error | Contact support if persistent | ### Common Issues #### File Upload Problems **Symptoms**: Files not processing or upload errors **Solutions**: - Verify file format is supported (Excel, Word, PDF, etc.) - Check file size is under 25MB - Ensure file is not corrupted or password-protected - Validate file metadata JSON format #### AI Generation Quality Issues **Symptoms**: Generated content is not relevant or useful **Solutions**: - Provide more specific and detailed AI prompts - Include relevant context in the aiHint parameter - Use templateId for additional context - Break complex requests into smaller, focused tasks #### Context Recognition Problems **Symptoms**: AI not understanding file content correctly **Solutions**: - Use selectedSheet parameter for Excel files - Specify relevant data ranges in file metadata - Ensure file content is clearly structured - Provide additional context in AI prompts ### Debugging Tips 1. **Start Simple**: Test with basic prompts before adding complexity 2. **Validate Files**: Ensure uploaded files are properly formatted and accessible 3. **Check Metadata**: Verify file metadata JSON is properly structured 4. **Monitor Responses**: Review mimeType and content format in responses 5. **Iterate Prompts**: Refine AI hints based on output quality ## Integration Patterns ### Template Workflow Integration Combine AI variable generation with template processing for complete automation: ```javascript // 1. Generate AI content const aiResponse = await generateAIVariable({ file: "financial-data.xlsx", aiHint: "Generate Q4 performance summary", richText: true, }); // 2. Use in template generation const templateData = { templateId: "quarterly-report-template", variables: [ { name: "Q4 Performance", placeholder: "{Q4Performance}", text: aiResponse.data.text, mimeType: aiResponse.data.mimeType, }, ], }; // 3. Generate final document const deliverable = await generateDeliverable(templateData); ``` ### Batch Processing Pattern Process multiple variables with AI for comprehensive content generation: ```javascript const aiVariables = [ { name: "Executive Summary", hint: "Create executive summary" }, { name: "Financial Analysis", hint: "Analyze financial metrics" }, { name: "Market Insights", hint: "Generate market insights" }, ]; const generatedContent = await Promise.all( aiVariables.map((variable) => generateAIVariable({ file: "company-data.xlsx", aiHint: variable.hint, name: variable.name, }) ) ); ``` ## Next Steps ### Advanced AI Features to Explore 📖 **[Template Generation API →](/docs/TurboDocx%20Templating/API%20Templates)** 📖 **[Webhook Integration →](/docs/Webhooks/webhook-configuration)** 📖 **[Bulk Processing →](/docs/Templates/bulk-generation)** 📖 **[API Authentication →](/docs/API/turbodocx-api-documentation)** ### Related Documentation - [Template Management Guide](/docs/Templates/template-management) - [Variable Types and Formatting](/docs/API/Deliverable%20API#variable-object-structure) - [Integration Examples](/docs/Integrations) - [Best Practices Guide](/docs/Templates/best-practices) ## Support Need help with AI-powered variable generation? We're here to help: - **Discord Community**: [Join our Discord server](https://discord.gg/NYKwz4BcpX) for real-time support - **Documentation**: [https://docs.turbodocx.com](https://docs.turbodocx.com) - **AI Examples**: Browse our example gallery for inspiration --- Ready to revolutionize your document workflows with AI-powered content generation? Start creating intelligent, context-aware variables that transform how you build documents! 🤖✨ --- ## Adding a New Product # Adding a New Product to a Quote This guide walks you through adding a new custom product to a sales quote in TurboQuote. You'll learn how to create a product record with category, billing frequency, and pricing details, then insert it as a line item in your quote. ## What You'll Accomplish By the end of this guide, you will have: - 🗂️ **Opened the product catalog** from within a quote - 📦 **Created a new product** with name, category, and billing frequency - 💵 **Set up list price** and optional cost for margin tracking - ➕ **Inserted the product** as a line item in your active quote :::tip Quick Start Promise Adding products to your catalog takes only a moment, and reusable products make every future quote faster to build. 🚀 ::: ## Step 1: Open the Add Product Interface Initiate the process of adding a product line item to the current quote by accessing the product selection interface. This sets the stage for searching, selecting, or creating products to enrich the quote. **Instruction:** - Click the **+ Add Product** button in the top action bar above the quote line items section. ![Open the Add Product interface](/img/turboquote/product-step-01.png) ## Step 2: Begin Creating a New Product Open the new product creation form when the desired product is not available in the existing catalog. This step enables adding a new product record to reuse in the quote and future transactions. **Instruction:** - In the Product Catalog dialog, click the **+ New Product** button located to the right of the search field. ![Begin creating a new product](/img/turboquote/product-step-02.png) ## Step 3: Focus the Product Name Field Set the cursor in the Product Name input field to begin specifying a clear and recognizable name for the new product. **Instruction:** - Click on the **Product Name** text input field near the top of the New Product form. ![Focus the Product Name field](/img/turboquote/product-step-03.png) ## Step 4: Select or Enter a Product Category Identify or create a category for the new product to organize it properly within the catalog for easier selection and reporting. **Instruction:** - Click on the **Category** text field in the New Product form to begin searching or entering a category. ![Select or enter a product category](/img/turboquote/product-step-04.png) ## Step 5: Choose a Category from the Dropdown Assign the new product to a category to align it with similar catalog items and facilitate proper organization. **Instruction:** - Click the desired category option (e.g. **Hardware (Default)**) from the Category dropdown list. ![Choose a category](/img/turboquote/product-step-05.png) ## Step 6: Open the Billing Frequency Dropdown Review or change the billing frequency to ensure it matches the intended sales and revenue model for the new product. **Instruction:** - Click on the **Billing Frequency** dropdown field currently showing **Monthly Recurring**. ![Open the Billing Frequency dropdown](/img/turboquote/product-step-06.png) ## Step 7: Select a Billing Frequency Switch the billing schedule to correspond with the product's sales type. **Instruction:** - Click on the desired option (e.g. **One-Time**) in the Billing Frequency dropdown list. ![Select billing frequency](/img/turboquote/product-step-07.png) ## Step 8: Navigate to the Pricing & Costs Tab Access the section to configure pricing details of the new product after setting general information. **Instruction:** - Click on the **Pricing & Costs** tab in the New Product dialog's tab bar. ![Navigate to Pricing & Costs tab](/img/turboquote/product-step-08.png) ## Step 9: Enter the List Price Focus on the List Price field to input or modify the product's list price amount for quoting. **Instruction:** - Click inside the **List Price** text input field in the Pricing & Costs tab. ![Enter the list price](/img/turboquote/product-step-09.png) ## Step 10: Enter the Optional Cost Value Specify the internal cost of the product to enable accurate margin tracking, if applicable. **Instruction:** - Click on the **Cost (Optional)** input field in the Pricing & Costs tab. ![Enter the optional cost value](/img/turboquote/product-step-10.png) ## Step 11: Submit the New Product Finalize and save the new product record to update the product catalog and make it available to add to the quote. **Instruction:** - Click the blue **Create Product** button at the bottom-right of the New Product dialog. ![Submit the new product](/img/turboquote/product-step-11.png) ## Step 12: Open Additional Options for the Product Access extra settings or detailed information about the new product within the catalog. **Instruction:** - Click on the circular icon button in the top-right corner of the product card. ![Open additional options](/img/turboquote/product-step-12.png) ## Step 13: Insert the Product into the Quote Complete the process by adding the created product as a line item in the active quote. **Instruction:** - Click the blue **+ Insert 1 Product** button at the bottom-right of the Product Catalog dialog. ![Insert the product into the quote](/img/turboquote/product-step-13.png) ## Summary You've successfully added a new custom product to your quote in TurboQuote! Here's a recap of what you accomplished: 1. Opened the product catalog from the quote interface 2. Created a new product with a name and category 3. Configured billing frequency and pricing details 4. Inserted the product as a line item in your active quote Your product is now saved in the catalog for future use and has been added to the current quote. You can continue adding more products or proceed to finalize your quote. --- # Creating a New Quote This guide walks you through the complete process of creating a new professional quote using TurboQuote. You'll learn how to initiate a quote, set up a company with contact information, and finalize the quote for your client. ## What You'll Accomplish By the end of this guide, you will have: - 🧾 **Created a new quote** from the TurboDocx home screen - 🏢 **Added a new company record** with industry classification - 👤 **Set up contact details** for the company - ✅ **Named and submitted** your completed quote :::tip Quick Start Promise Creating your first quote takes just a few minutes. Follow along and you'll have a professional, ready-to-send quote in no time! 🚀 ::: ## Step 1: Start Creating a New Quote from the Main TurboDocx Home Screen Begin the process of creating and sending a new professional quote using TurboQuote. The main options section provides the entry point to initiate this workflow, setting up the workspace to add client details, products, and pricing. **Instruction:** - Click on the **Create a Quote** card in the main options section to start building a new quote. ![Start creating a new quote from the home screen](/img/turboquote/step-01.png) ## Step 2: Initiate Quote Creation from the TurboQuote Quotes List Page Access the TurboQuote module directly to begin creating a new quote. This will transition to the quote creation interface where detailed quote information can be provided. **Instruction:** - Click the **+ New Quote** button located in the top-right toolbar of the quotes page. ![Initiate quote creation from the quotes list](/img/turboquote/step-02.png) ## Step 3: Open the Add New Menu on the Quote Creation Page Access options to add new contacts or related items while building the quote. This menu exposes additional creation flows necessary for completing the quote setup. **Instruction:** - Click the purple **Add New** button on the quote page to reveal more creation options. ![Open the Add New menu](/img/turboquote/step-03.png) ## Step 4: Select New Company from the Add New Dropdown Create a new company record to associate with the quote. This ensures the quote is linked to an up-to-date company profile for accurate tracking and communication. **Instruction:** - Click the **New Company** option in the dropdown menu under the **Add New** button. ![Select New Company from dropdown](/img/turboquote/step-04.png) ## Step 5: Focus the Company Name Field in the Create Company Form Begin entering the company's name, which is the primary identifier for the new company record. **Instruction:** - Click on the **Company Name** text input field at the top of the Create Company form to activate text entry. ![Focus the Company Name field](/img/turboquote/step-05.png) ## Step 6: Open Industry Selection Options in Create Company Form Access the interface to select an industry or create a new one, specifying the business category associated with the company. **Instruction:** - Click the icon button at the right side of the **Select or create industry...** field within the Create Company form. ![Open industry selection options](/img/turboquote/step-06.png) ## Step 7: Choose an Industry from the Dropdown Define the company's industry by selecting the appropriate preset industry from the dropdown list. **Instruction:** - Click on the desired industry option (e.g. **Construction (Default)**) in the Industry dropdown list to select it for the company. ![Choose industry from dropdown](/img/turboquote/step-07.png) ## Step 8: Edit the Contact Name Field in the Create Company Form Focus on the primary contact's name field to enter or modify the contact person's full name. **Instruction:** - Click on the **Name** text input field under the **Contact 1** section in the Create Company form to activate the text cursor. ![Focus the contact name field](/img/turboquote/step-08a.png) ![Enter the contact name](/img/turboquote/step-08b.png) ## Step 9: Enter the Contact Email Address Prepare to enter or update the email address for the primary contact, which is essential for communication related to the quote. **Instruction:** - Click on the email address text input field under **Contact 1** in the Create Company form to focus it for editing. ![Enter the contact email address](/img/turboquote/step-09.png) ## Step 10: Submit the Create Company Form Complete the creation of the new company record along with its associated contact details by submitting the form. **Instruction:** - Click the **Create Company** button located at the bottom-right of the modal to submit the form. ![Submit the Create Company form](/img/turboquote/step-10.png) ## Step 11: Select the Contact Card for the New Quote Associate the newly created contact with the quote by selecting their contact card. **Instruction:** - Click on the contact card labeled with the contact's name to select this contact for the quote. ![Select the contact card](/img/turboquote/step-11.png) ## Step 12: Submit the Completed Quote Finalize and create the new quote record by submitting the detailed quote information after linking company and contact. **Instruction:** - Click the blue **Create Quote** button at the bottom-right of the Quote Details dialog to submit and create the quote. ![Submit the completed quote](/img/turboquote/step-12.png) ## Step 13: Name Your Quote Name the quote appropriately to identify it clearly for future reference and client communication. **Instruction:** - Click on the **Quote Name** text input field at the top of the Quote Details modal to begin typing the quote name. ![Focus the quote name field](/img/turboquote/step-13a.png) ![Enter the quote name](/img/turboquote/step-13b.png) ## Summary You've successfully created a new quote in TurboQuote! Here's a recap of what you accomplished: 1. Initiated a new quote from the TurboDocx home screen or TurboQuote quotes list 2. Created a new company record with industry classification 3. Added contact details including name and email 4. Selected the contact for the quote and submitted the final quote Your quote is now ready for you to add line items, pricing, and any additional details before sending it to your client. --- # 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 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_ --- # TurboSign API Integration 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 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. ### Two Endpoint Options 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 - **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 :::tip Prefer using an SDK? We offer official SDKs that handle authentication, error handling, and type safety for you. [View all SDKs →](/docs/SDKs) ::: ## TLDR; Complete Working Example 🚀 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 | | `checkbox` | Checkbox field | Acknowledgments, consent, agreements | ### Quick Start: Prepare for Signing (Most Common) Use this endpoint to send documents immediately for signing: ### Alternative: Prepare for Review Use this endpoint when you need a preview URL to verify field placement: ### Quick Comparison | Feature | prepare-for-review | prepare-for-signing | | -------------------- | ------------------------------ | ------------------------- | | Sends emails? | ❌ No | ✅ Yes | | Returns preview URL? | ✅ Yes | ❌ No | | Returns recipients? | ✅ Yes | ✅ Yes | | 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... ## Prerequisites Before you begin, ensure you have: - **API Access Token**: Bearer token for authentication - **Organization ID**: Your organization identifier - **PDF Document**: Document ready for signature collection ### 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 API requests require authentication using a Bearer token in the Authorization header: ```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 ``` ## Choosing Your Endpoint TurboSign offers two single-step endpoints to fit different workflows. Choose the one that best matches your use case. ### 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://api.turbodocx.com/turbosign/single/prepare-for-review ``` ### 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**: 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 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 { "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 | | ---------- | ------------- | ---------------------------------------------- | | 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 ### 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 ## 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://api.turbodocx.com/turbosign/single/prepare-for-signing ``` ### 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) The request format is **identical** to prepare-for-review. See the "Endpoint 1: Prepare for Review" section above for detailed field documentation. ### Response ```json { "success": true, "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", "status": "UNDER_REVIEW", "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 sent for signing. Emails are being sent to recipients." } ``` ### Response Fields | Field | Type | Description | | ---------- | ------------- | ---------------------------------------------- | | success | Boolean | Request success status | | documentId | String (UUID) | Unique document identifier - save for tracking | | status | String | Document status (UNDER_REVIEW) | | recipients | Array | Array of recipient objects with generated IDs | | 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 ## Endpoint 3: Download Signed Document After a document has been signed by all recipients (status: `COMPLETED`), you can download the final signed PDF document. ### Endpoint ```http GET https://api.turbodocx.com/turbosign/documents/{documentId}/download ``` ### 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 | | ---------- | ------------- | -------- | -------------------------------- | | documentId | String (UUID) | Yes | The unique identifier of the document | ### Response ```json { "downloadUrl": "https://s3.amazonaws.com/bucket/path/to/document.pdf?X-Amz-...", "fileName": "Signed_Contract_2024.pdf" } ``` ### Response Fields | Field | Type | Description | | ----------- | ------ | ---------------------------------------------------------- | | downloadUrl | String | Presigned S3 URL to download the signed PDF (expires in 1 hour) | | fileName | String | Original filename of the signed document | :::warning Document Status Requirement This endpoint only returns a download URL when the document status is `COMPLETED`. If the document is still pending signatures, you will receive an error response. ::: ### Usage Notes - The presigned URL expires after **1 hour**. Request a new URL if the previous one has expired. - The downloaded PDF includes all signatures embedded and is legally binding. - For large documents, consider streaming the download rather than loading the entire file into memory. ## Endpoint 4: Get Audit Trail Retrieve the complete audit trail for a document, including all events and timestamps. The audit trail provides a tamper-evident record of all actions taken on the document using a cryptographic hash chain. ### Endpoint ```http GET https://api.turbodocx.com/turbosign/documents/{documentId}/audit-trail ``` ### 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 | | ---------- | ------------- | -------- | -------------------------------- | | documentId | String (UUID) | Yes | The unique identifier of the document | ### Response ```json { "data": { "document": { "id": "4a20eca5-7944-430c-97d5-fcce4be24296", "name": "Service Agreement 2024" }, "auditTrail": [ { "id": "entry-uuid-1", "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", "actionType": "prepared_for_review", "timestamp": "2024-01-15T10:30:00.000Z", "previousHash": null, "currentHash": "a1b2c3d4e5f6...", "createdOn": "2024-01-15T10:30:00.000Z", "details": { "ipAddress": "192.168.1.100", "userAgent": "Mozilla/5.0..." }, "user": { "name": "Admin User", "email": "admin@company.com" }, "userId": "user-uuid-1" }, { "id": "entry-uuid-2", "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", "actionType": "document_sent", "timestamp": "2024-01-15T10:31:00.000Z", "previousHash": "a1b2c3d4e5f6...", "currentHash": "b2c3d4e5f6g7...", "createdOn": "2024-01-15T10:31:00.000Z", "details": { "recipientCount": 2 }, "user": { "name": "Admin User", "email": "admin@company.com" }, "userId": "user-uuid-1" }, { "id": "entry-uuid-3", "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", "actionType": "document_viewed", "timestamp": "2024-01-15T11:00:00.000Z", "previousHash": "b2c3d4e5f6g7...", "currentHash": "c3d4e5f6g7h8...", "createdOn": "2024-01-15T11:00:00.000Z", "details": { "ipAddress": "10.0.0.50" }, "recipient": { "name": "John Smith", "email": "john.smith@company.com" }, "recipientId": "recipient-uuid-1" }, { "id": "entry-uuid-4", "documentId": "4a20eca5-7944-430c-97d5-fcce4be24296", "actionType": "document_signed", "timestamp": "2024-01-15T11:05:00.000Z", "previousHash": "c3d4e5f6g7h8...", "currentHash": "d4e5f6g7h8i9...", "createdOn": "2024-01-15T11:05:00.000Z", "details": { "signatureType": "electronic", "ipAddress": "10.0.0.50" }, "recipient": { "name": "John Smith", "email": "john.smith@company.com" }, "recipientId": "recipient-uuid-1" } ] } } ``` ### Response Fields #### Document Object | Field | Type | Description | | ----- | ------------- | --------------------------- | | id | String (UUID) | Document identifier | | name | String | Document name | #### Audit Trail Entry Object | Field | Type | Description | | ------------ | ------------- | ----------------------------------------------------- | | id | String (UUID) | Unique identifier for the audit entry | | documentId | String (UUID) | Document this entry belongs to | | actionType | String | Type of action (see Action Types below) | | timestamp | String (ISO) | When the action occurred | | previousHash | String | Hash of the previous entry (null for first entry) | | currentHash | String | Hash of this entry (forms hash chain) | | createdOn | String (ISO) | When the entry was created | | details | Object | Additional action-specific details | | user | Object | User who performed action (for sender actions) | | userId | String (UUID) | User ID (when applicable) | | recipient | Object | Recipient who performed action (for signer actions) | | recipientId | String (UUID) | Recipient ID (when applicable) | #### Action Types | Action Type | Description | | ----------------------------- | --------------------------------------------------- | | `prepared_for_review` | Document was uploaded and prepared for review | | `document_sent` | Document was sent to recipients for signing | | `document_viewed` | Recipient opened/viewed the document | | `document_signed` | Recipient signed the document | | `document_voided` | Document was voided/cancelled | | `document_resent` | Reminder/resend email was sent to recipient | | `email_notification_sent` | Signing invitation email was sent | | `cc_email_notification_sent` | CC notification email was sent | ### Hash Chain Verification The audit trail uses a cryptographic hash chain for tamper-evidence: - Each entry's `currentHash` is computed from the entry data plus the `previousHash` - The first entry has `previousHash: null` - To verify integrity, recompute each hash and compare - Any modification to historical entries would break the chain This provides strong evidence that the audit trail has not been tampered with after creation. ### Usage Notes - The audit trail is available at any document status (not just completed documents) - All timestamps are in ISO 8601 format (UTC timezone) - The `details` object varies by action type and may contain IP addresses, user agents, and other contextual information - Audit trail entries are immutable and cannot be modified or deleted ## Recipients Reference ### Recipient Properties Each recipient object in the `recipients` array should contain the following properties: | 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) | ### Metadata Object (Optional) The `metadata` object allows you to customize the recipient's UI appearance: | 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 [ { "name": "John Smith", "email": "john.smith@company.com", "signingOrder": 1 }, { "name": "Jane Doe", "email": "jane.doe@partner.com", "signingOrder": 2 } ] ``` ### With Optional Metadata ```json [ { "name": "John Smith", "email": "john.smith@company.com", "signingOrder": 1, "metadata": { "color": "hsl(200, 75%, 50%)", "lightColor": "hsl(200, 75%, 93%)" } } ] ``` ## 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 { "anchor": "{Signature1}", "placement": "replace", "size": { "width": 200, "height": 80 }, "offset": { "x": 10, "y": -5 } // 10px right, 5px up from anchor } ``` ### Method 2: Coordinate-based Positioning Uses exact pixel coordinates to position fields on specific pages. Best for precise control or when anchors aren't feasible. #### Advantages ✅ Pixel-perfect precision ✅ Works with PDFs that can't be edited ✅ Programmatically generated positions ✅ Useful for form-filling scenarios ✅ Consistent placement across documents #### How it Works 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** #### Coordinate Configuration Example ```json { "recipientEmail": "john.smith@company.com", "type": "signature", "page": 1, "x": 100, "y": 200, "width": 200, "height": 80, "pageWidth": 612, "pageHeight": 792, "required": true } ``` #### Coordinate System Reference - **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 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 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 **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 **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**: - 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 ### 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 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 ### Common HTTP Status Codes | Status Code | Description | Solution | | ----------- | --------------------- | --------------------------------------------- | | `200` | Success | Request completed successfully | | `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 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 #### JSON String Formatting Errors **Symptoms**: 400 Bad Request with message "Invalid JSON in recipients/fields" **Solutions**: - ✅ 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) **Example Error Response**: ```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**: - ✅ 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) **Example Error Response**: ```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 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 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 - ✅ 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` #### Webhook Integration Issues **Symptoms**: Not receiving completion notifications **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 (webhook.site, ngrok) - ✅ Implement HMAC signature verification ### Debugging Tips 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 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)** ### Related Documentation - [TurboSign Setup Guide](/docs/TurboSign/Setting%20up%20TurboSign) - [Webhook Configuration](/docs/TurboSign/Webhooks) - [API Authentication](/docs/API/turbodocx-api-documentation) - [Integration Examples](/docs/Integrations) ## Support Need help with your integration? - **Discord Community**: [Join our Discord server](https://discord.gg/NYKwz4BcpX) for real-time support and discussions - **Documentation**: [https://docs.turbodocx.com](https://docs.turbodocx.com) --- 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! --- # Bulk Signature Sending Need to send the same document to dozens — or hundreds — of recipients, each with their own personalized details and signature fields? The **Bulk Send** wizard lets you do exactly that, right from the TurboDocx UI. Upload a spreadsheet, map your columns, preview the results, and submit the entire batch in minutes. ## What You'll Learn - 📊 **Upload a spreadsheet** (CSV or XLSX) with recipient data - 🔗 **Map columns** to template variables automatically or manually - ✏️ **Configure signature fields** with types, defaults, and recipient assignments - 👁️ **Preview merged documents** before sending - 📤 **Submit batches** and monitor progress - 📈 **Track batch status** from the Batches dashboard :::info Pro Plan Required Bulk Signature Sending is available exclusively on the **Pro plan**. If you're on a free or starter plan, clicking the Bulk Send button will prompt you to upgrade. [Learn more about Pro plan features](https://www.turbodocx.com/pricing). ::: ## Before You Begin To use Bulk Signature Sending, you'll need: - **Pro plan** — the feature is gated to Pro subscribers - **A TurboDocx template** — with variables (placeholders) that correspond to columns in your spreadsheet - **A spreadsheet** — CSV (`.csv`) or Excel (`.xlsx`) file with one row per recipient/document - **Sufficient signature credits** — each recipient in each document consumes 1 credit :::tip Prepare Your Spreadsheet Make sure your spreadsheet has clear column headers that match (or are close to) your template's placeholder names. The wizard will try to auto-match them, but clean headers make the process much faster. ::: --- ## Step 1: Open the Bulk Send Wizard Navigate to the **Edit Template & Preferences** page for the template you want to send in bulk. You'll see a **"Bulk Send"** button alongside the other template actions. **Click "Bulk Send"** to open the wizard. ![Bulk Send button on Edit Template & Preferences page](/img/turbosign/bulk/bulk-send-button.jpeg) :::note Non-Pro Users If your organization doesn't have the Pro plan, the Bulk Send button displays an upgrade badge. Clicking it opens the upgrade dialog instead of the wizard. ::: --- ## Step 2: Upload Your Spreadsheet The first step of the wizard is uploading your recipient data. ### How to Upload - **Drag and drop** your file onto the upload area, or - **Click to browse** and select a file from your computer ### Supported Formats | Format | Extension | Notes | |--------|-----------|-------| | CSV | `.csv` | Comma-separated values | | Excel | `.xlsx` | Multi-sheet support — select which sheet to use | ### What You'll See After uploading, the wizard displays: - **File name** and format confirmation - **Sheet selector** (for Excel files with multiple sheets) - **Preview table** showing the first 5 rows of your data with column headers ![Drag and drop or browse to upload your spreadsheet](/img/turbosign/bulk/spreadsheet-upload.jpeg) Review the preview table to confirm your data looks correct, then click **Next**. ![Spreadsheet preview showing column headers and first rows](/img/turbosign/bulk/spreadsheet-preview.jpeg) :::tip File Tips - **Headers matter** — the first row of your spreadsheet should contain column headers, not data - **Keep it clean** — remove empty rows or columns before uploading - **Check encoding** — for CSV files, use UTF-8 encoding to avoid character issues ::: --- ## Step 3: Map Columns & Configure Recipients This is the most important step. Here you'll connect your spreadsheet data to your template's placeholders, set up signature fields, and define who receives each document. ### 3a. Map Template Variables Each template variable (placeholder) needs to be resolved — either by mapping it to a spreadsheet column or by designating it as a signature field. ![Template variable mapping with auto-match](/img/turbosign/bulk/column-mapping.jpeg) **Auto-Matching:** The wizard automatically attempts to match your column headers to template placeholders using fuzzy matching. For example, a column named `client_email` would auto-match to a `{clientEmail}` placeholder. Auto-matched mappings display an **"Auto-matched"** chip. **Manual Override:** You can always override an auto-match by selecting a different column from the dropdown. **Signature Field Option:** Instead of mapping a variable to a column, you can click the **"Sign"** button next to a variable to designate it as a **signature field** — meaning it becomes a field that the recipient fills in or signs during the signing process. ![Click the Sign button to designate a variable as a signature field](/img/turbosign/bulk/signature-field-toggle.jpeg) ### 3b. Configure Signature Fields For any variable designated as a signature field, you'll configure: **Field Type** — choose from the following: | Field Type | Description | |------------|-------------| | **Signature** | Full signature capture | | **Initial** | Initials field | | **Date** | Date picker | | **Name** | Full name text field | | **First Name** | First name only | | **Last Name** | Last name only | | **Email** | Email address field | | **Title** | Job title field | | **Company** | Company name field | | **Text Input** | Free-form text entry | | **Checkbox** | Checkbox confirmation | **Recipient Assignment** — assign the field to a specific recipient (signer). **Optional Settings:** - **Default Value** — pre-fill the field with a value the signer can review or edit - **Read Only** — lock the field so the signer can see it but not change it (requires a default value) - **Multiline** — for text fields, allow multi-line input ### 3c. Add Recipients Define who signs each document by mapping spreadsheet columns to recipient slots. **For each recipient, configure:** - **Email Column** — which spreadsheet column contains their email address - **Name Column** — which spreadsheet column contains their name **Additional options:** - **Multiple Recipients** — click **"Add Recipient"** to add up to **10 signers** per document. Each recipient is color-coded for easy visual identification - **CC Email Column** — optionally select a column containing CC email addresses (supports comma or semicolon-separated lists for multiple CCs per row) ![Recipient configuration with CC email mapping](/img/turbosign/bulk/recipient-config.jpeg) ### 3d. Document Naming (Optional) Build dynamic document names for each generated document using the name builder toolbar: - **Insert Column** — pull in a value from your spreadsheet (e.g., a "DocumentTitle" column) - **Insert Date** — append the current date - **Add Separator** — insert a separator character (hyphen, underscore, slash, etc.) - **Static text** — type fixed text directly Drag segments to reorder them. The wizard shows a live preview of the resulting document name using data from your first row. ![Document name builder with Insert Column, Insert Date, and Add Separator controls](/img/turbosign/bulk/document-naming.jpeg) ### 3e. Validation The wizard validates your configuration before allowing you to proceed. If there are issues, you'll see red alert banners explaining what needs to be fixed. **Common validation requirements:** | Requirement | What It Means | |-------------|---------------| | At least one signature field | You need at least one variable designated as a signature field | | All variables resolved | Every template variable must either map to a column, be a signature field, or explicitly set to empty | | Recipient email & name mapped | Each recipient slot needs both an email and name column | | Valid email addresses | All emails in mapped email columns must be valid | | Each recipient has a signature field | Every recipient needs at least one field assigned to them | | Editable fields per recipient | Each recipient must have at least one field that isn't read-only | | Read-only fields need defaults | Any read-only field must have a default value set | :::tip Fixing Validation Errors Read each alert carefully — they tell you exactly which recipient or field has the issue. Most problems are resolved by assigning a missing column mapping or adding a signature field to a recipient. ::: --- ## Step 4: Preview Before submitting, you can preview exactly what each merged document will look like. ### What You'll See - **PDF Preview** — a rendered preview of the document with your spreadsheet data merged in - **Variable Table** — a side-by-side table showing which values were inserted for the current row - **Row Navigator** — **Previous** and **Next** buttons to cycle through rows and check different recipients' documents Use this step to verify that: - Variables are merging correctly - The right data appears in the right places - The document looks as expected ![Document preview showing merged variable values](/img/turbosign/bulk/preview.jpeg) :::tip Preview Tips - **Check multiple rows** — don't just preview the first row. Navigate through several rows to spot formatting issues or empty fields - **Watch for long text** — verify that longer values don't overflow or break the document layout ::: --- ## Step 5: Review & Submit The final step before sending your batch. ### What to Review - **Batch Name** — give your batch a descriptive name for easy tracking (defaults to the template name) - **Recipient Summary** — review the first several rows showing recipient names and emails - **Document Count** — confirms how many documents will be generated and sent ### Submitting Click **"Submit"** to initiate batch processing. The wizard shows a loading state while submitting. **On success:** the dialog closes and your batch begins processing in the background. You can monitor progress from the Batches dashboard. ![Submit Batch button on the review screen](/img/turbosign/bulk/submit-batch.jpeg) --- ## Monitoring Your Batch After submitting, navigate to the **Batches** dashboard to track your batch. ### How to Get There Go to **E-Signatures** in the sidebar, then click the **"Bulk Batches"** tab to navigate to the batch management page. ![Navigate to E-Signatures and click Bulk Batches](/img/turbosign/bulk/bulk-batches-nav.jpeg) ### Batch Dashboard The dashboard shows all your batches with: | Column | Description | |--------|-------------| | **Batch Name** | The name you assigned during submission | | **Status** | Current batch status (see below) | | **Jobs** | Total / Succeeded / Failed / Pending counts | | **Created** | When the batch was submitted | | **Updated** | When the batch was last updated | ### Batch Statuses | Status | Icon | Description | |--------|------|-------------| | **Pending** | ⏳ | Batch is queued and waiting to start processing | | **Processing** | ⏳ | Documents are being generated and sent | | **Completed** | ✅ | All jobs finished (some may have failed) | | **Failed** | ❌ | Batch encountered a critical error | | **Cancelled** | ❌ | Batch was cancelled by user | ### Filtering & Search - **Search** by batch name - **Filter** by status group and time period - **Sort** by any column - **Paginate** through large result sets ![Batches dashboard showing batch status and job counts](/img/turbosign/bulk/batches-dashboard.jpeg) --- ## Tips & Best Practices ### Spreadsheet Preparation - **Use clear column headers** that closely match your template placeholder names — this maximizes auto-matching accuracy - **Validate email addresses** before uploading — invalid emails cause individual job failures - **Remove empty rows** at the bottom of your spreadsheet - **Keep one row per document** — each row generates one document with one set of recipients ### Start Small - **Test with 5-10 rows first** to verify your configuration before scaling up - **Preview multiple rows** to catch edge cases - **Check the Batches dashboard** after your test batch to confirm all jobs succeeded ### Signature Fields - **Each recipient needs at least one editable signature field** — the system enforces this to ensure every signer has something to complete - **Use read-only fields** for information the signer should see but not change (like a pre-filled address) - **Set sensible defaults** for text fields when you already know the value ### Credit Management - **1 credit per document** — a batch of 100 documents uses 100 credits, regardless of how many recipients each document has - **Credits are reserved** when you submit and **refunded** for any failed or cancelled jobs - **Check your credit balance** before submitting large batches --- ## Troubleshooting ### "The Next Button Is Disabled on Step 3" **Cause:** One or more validation requirements aren't met. **Solution:** Scroll through the step and look for red alert banners. They describe exactly what's missing — usually an unmapped variable, a recipient without a signature field, or an invalid email address. ### Preview PDF Isn't Loading **Cause:** The preview generation may be taking time for complex templates. **Solution:** Wait a few moments. If it persists, try navigating to a different row and back. Ensure your template doesn't have errors that would prevent document generation. ### Batch Stuck in "Processing" **Cause:** Large batches take time to process. Each document goes through generation, PDF conversion, and signature request creation. **Solution:** Wait for processing to complete — most batches finish within 30 minutes. Check individual job statuses in the Batches dashboard. If stuck for over an hour, contact support. ### Some Jobs Failed in My Batch **Cause:** Individual jobs can fail due to invalid email addresses, template errors for specific data, or transient issues. **Solution:** Check the failed job's error message in the Batches dashboard. Fix the data issue and submit a new batch with just the corrected rows. --- ## What's Next? Now that you know how to send bulk signatures from the UI, explore these related features: - **[How to Get a Document Signed](/docs/TurboSign/Setting%20up%20TurboSign)** — the single-document signing flow for one-off requests - **[TurboSign Bulk API Integration](/docs/TurboSign/API%20Bulk%20Signatures)** — send bulk signatures programmatically via API - **[Webhooks](/docs/TurboSign/Webhooks)** — get real-time notifications when documents are signed - **[Managing Your Signatures](/docs/TurboSign/Managing%20Your%20Signatures)** — track, resend, void, and download signed documents --- # Managing Your Signatures Once you've sent your document for signature, your work isn't done! This guide covers everything you need to know about managing your signature requests, tracking progress, and handling any issues that come up. ## What You'll Learn - 📊 **Track signature progress** and see who's signed - 📧 **Resend signature emails** when needed - ❌ **Void documents** that need to be cancelled - 🔍 **View audit trails** for complete signature history - 📄 **Download completed documents** when all signatures are collected ## Quick Reference: Common Actions **Access all document actions through the context menu (three-dot menu ⋮):** | Action | Purpose | |--------|---------| | **Void Document** | Cancel signature request | | **Resend Email** | Send reminder to recipients | | **Download PDF** | Get fully signed document | | **Audit Trail** | View complete activity history | --- ## Tracking Signature Progress & Viewing Audit Trails ### Automatic Email Delivery **Good news!** TurboSign automatically sends a copy of the completed document and the user-facing audit trail to all participants via email when the signature process is complete. This means: - **All signers** get their own copy of the completed document - **Email delivery** happens instantly when the last signature is completed - **Audit trail** is included for compliance and record-keeping So while you can always redownload documents from your dashboard, you likely already have the completed document in your email inbox! --- ## How to Resend Signature Emails Need to send a gentle reminder to recipients who haven't signed yet? TurboSign makes it easy to resend signature requests to the next person in your signing order. ### Step-by-Step: Resending Signature Emails **Step 1: Access the Document Actions** 1. Navigate to your **TurboSign Dashboard** 2. Find the document with pending signatures 3. Click the **three-dot menu (⋮)** next to the document ![Document context menu showing resend email option](/img/turbosign/ResendEmail.png) **Step 2: Send the Reminder** 4. Click **"Resend Email"** from the context menu 5. The resend modal will open showing available recipients ![Resend email modal showing recipient selection](/img/turbosign/ResendEmailHighlighted.png) 6. Click **"Send Email"** to dispatch the email 7. The dialog will close and the recipient will receive a reminder email with their signing link --- ## How to Void a Document Need to cancel a signature request that's already been sent? TurboSign makes it easy to void documents and notify all recipients. ### Step-by-Step: Voiding a Document **Step 1: Access the Document Actions** 1. Navigate to your **TurboSign Dashboard** 2. Locate the document you want to void 3. Click the **three-dot menu (⋮)** next to the document ![Document context menu showing void option](/img/turbosign/VoidDocument.png) **Step 2: Enter Void Reason** 4. Enter a void reason that explains why you are voiding the document. ![Void dialog with reason field](/img/turbosign/VoidDialogReasonsForVoiding.png) **Step 3: Confirm Void** 5. Click the **"Void Document"** button in the bottom right corner to confirm ![Clicking void document confirmation button](/img/turbosign/ClickVoidDocument.png) 6. All recipients receive automatic notifications and the document status changes to "Voided" --- ## How to Redownload Completed Documents Need to download your fully signed document again? TurboSign makes it easy to redownload the completed PDF with all signatures whenever you need it. ### Step-by-Step: Redownloading Your Signed Document **Step 1: Access the Document Actions** 1. Navigate to your **TurboSign Dashboard** 2. Look for documents with **green "Completed" status** 3. Click the **three-dot menu (⋮)** next to your completed document ![Document context menu showing download PDF option](/img/turbosign/DownloadPDF.png) **Step 2: Download the PDF** 4. Click **"Download PDF"** from the context menu 5. Your browser will automatically start downloading the file with all signatures --- ## How to View Audit Trails Need to see the complete history of your document? TurboSign maintains a detailed audit trail of every action taken. ### Step-by-Step: Accessing Audit Trails **Step 1: Access the Document Actions** 1. Open your **TurboSign Dashboard** 2. Find any document (completed or in-progress) 3. Click the **three-dot menu (⋮)** next to the document ![Document context menu showing audit trail option](/img/turbosign/AuditTrail.png) **Step 2: View the Timeline** 4. Click **"Audit Trail"** from the context menu 5. The audit trail modal opens showing a chronological timeline ![Audit trail overview showing complete timeline](/img/turbosign/AuditTrailOverview.png) 6. Review all events with timestamps, participants, and security details. **Understanding Event Types:** The audit trail displays two main categories of events: **User Events** - Actions taken by document participants: - **Document Viewed** - When recipients open and view the document - **Document Signed** - When recipients complete their signature These are the two most important user events to monitor for tracking signature progress: ![Document viewed event details](/img/turbosign/AuditTrailDocumentViewed.png) ![Document signed event details](/img/turbosign/AuditTrailDocumentSigned.png) **System Events** - Automated actions by TurboSign: - **Email Notification Sent** - When the system sends emails to recipients - **Document Digitally Signed by TurboSign** - When TurboSign applies digital signatures - **Document Updated** - When the document is modified or updated via signature - **Document Sent** - When the document is initially distributed for signature ### How to Redownload the Audit Trail We already send a pretty user-facing Audit Trail with User events, but if you want to download a detailed copy, click **Download Audit Trail as JSON**, --- --- *Need help with signature management? Each action includes detailed step-by-step guides, screenshots, and best practices for professional document handling.* --- # How to Get a Document Signed with TurboSign Ready to send your first document for e-signature? You're in the right place! This step-by-step guide will walk you through the entire process of preparing and sending your document using TurboSign. ## What You'll Accomplish By the end of this guide, you'll know how to: - 📄 **Upload or select documents** for signature - 👥 **Add recipients** and manage signing order - ✏️ **Place signature fields** exactly where you need them - 📤 **Send documents** for signature with confidence - 🔄 **Track the signing process** from start to finish :::tip Quick Start Promise This entire process takes less than 5 minutes once you get the hang of it. We'll have you sending professional signature requests in no time! 🚀 ::: ## Before You Begin To use TurboSign effectively, you'll need: - Access to TurboDocx - A document ready for signing (PDF, Word, or existing TurboDocx deliverable) - Email addresses for all recipients who need to sign - About 60 seconds of your time ⏰ :::tip Pro Tip Have your document ready and know exactly where signatures are needed before you start. This makes the whole process much smoother! ::: ## Step 1: Click "Get It Signed" Let's start by getting to TurboSign from your main dashboard. From the **TurboDocx homepage**, look for the **"Get It Signed"** button and click it. This will take you directly to **TurboSign** where all the magic happens. ![Get It Signed button on TurboDocx homepage](/img/turbosign/GetSigned.png) ## Step 2: Start a New Signature Request Now that you're in TurboSign, it's time to create your first signature request. In the **top right corner** of the TurboSign interface, you'll see a **"New Signature"** button. Click it to begin preparing your document for signatures. ![New Signature button on TurboSign dashboard](/img/turbosign/ClickNewSignature.png) ## Step 3: Upload or Select a Document Time to choose your document! TurboSign gives you two convenient options: ### Option A: Upload a New Document **Upload a new document** from your computer by clicking the upload area or dragging and dropping your file. **Supported formats:** - PDF files (.pdf) - Word documents (.docx) - Other common document formats ### Option B: Select an Existing Deliverable **Select an existing deliverable** you've already created within TurboDocx. This is perfect if you've already generated a document and want to get it signed. ![Document upload and selection interface](/img/turbosign/SelectOrUploadDocument.png) :::tip Document Tips - **PDF files work best** for consistent formatting across all devices - **Keep file sizes reasonable** (under 10MB) for faster loading - **Make sure your document is final** before uploading - you can't edit content after this step ::: ## Step 4: Add Document Details Now let's add some important information about your document. Fill in the required information: ### Document Name (Required) Give your document a clear, descriptive name. This will help you and your recipients identify the document easily. **Good examples:** - "Q3 Marketing Agreement - ABC Corp" - "Employment Contract - John Smith" - "Service Agreement - Project Alpha" ### Description (Optional) Add a brief description to provide context for your recipients. This appears in the signature request email. **Good examples:** - "Please review and sign this quarterly marketing agreement" - "New employee contract for review and signature" - "Service agreement for the upcoming project" ![Document details form with name and description fields](/img/turbosign/DocumentNameAndDescription.png) ## Step 5: Add Recipients Time to add the people who need to sign your document. This is where you control who signs and in what order. ### Adding Recipients **Click "Add Recipient"** to assign others to sign the document. You can add multiple recipients as needed. For each recipient, you'll need: - **Full name** (as it should appear on the signature) - **Email address** (where they'll receive the signature request) ### Including Yourself **Click "Include Me"** if you also need to sign the document. This adds you to the recipient list automatically. ### Setting Signing Order **Drag to change the signing order** if it matters for your document. The signing order determines who gets the document first. **When order matters:** - Legal documents that require witness signatures - Contracts where one party must sign before the other - Documents with approval workflows **When order doesn't matter:** - Simple agreements between equal parties - Documents where all parties can sign simultaneously ![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 - **Use professional email addresses** for business documents - **Double-check email addresses** - typos mean delayed signatures - **Add yourself first** if you need to sign before others - **Keep signing order simple** unless legally required ::: ## Step 6: Continue to Field Placement Almost there! Now we'll move to the most important part - telling TurboSign exactly where signatures and other information should go. **Click "Continue"** in the bottom right corner to move to the field placement interface. This is where you'll drag and drop signature fields onto your document. ![Continue button at bottom right of recipient setup](/img/turbosign/AddRecipients.png) ## Step 7: Place Signature Fields Here's where the magic happens! You'll drag and drop the necessary fields onto your document exactly where you want them. ### Available Field Types The field editor provides several types of fields you can add: **Essential Fields:** - **Signature** - The actual signature field - **Name** - Typed name field - **Date** - Date when signed - **Initials** - For initialing pages or sections **Additional Fields:** - **Text** - For additional information - **Checkbox** - For confirmations or agreements - **Dropdown** - For selecting from options ![Field editor with available field types on the left](/img/turbosign/FieldEditorDragAndDrop.png) ### How to Place Fields 1. **Drag the field type** from the left panel 2. **Drop it onto the document** where you want it to appear 3. **Resize if needed** by dragging the corner handles 4. **Assign to the correct recipient** using the dropdown ### Assigning Fields to Recipients **Each field must be assigned to a recipient.** Make sure you: - Assign signature fields to the correct person - Place fields in logical locations on the document - Ensure all required fields are present ![Document with signature fields placed and recipient assignments](/img/turbosign/DraggingAndDroppingFields.png) :::tip Field Placement Tips - **Place signature fields near signature lines** if your document has them - **Add date fields near signatures** for legal completeness - **Use initials fields** for multi-page documents - **Test field sizes** - they should be large enough for signatures but not overwhelming - **Group related fields** together for better user experience ::: ## Step 7.5: Setting Default Values (Optional) Want to save your recipients time by pre-filling some information? TurboSign lets you set default values that will automatically appear in fields when recipients open the document. This is especially useful for common information you already know about your signers. ### What Are Default Values? **Default values** are pre-filled text that appears in fields automatically. Recipients can see this information already filled in and: - **Keep it as-is** if it's correct - **Edit it** if they need to make changes - **Save time** by not typing common information from scratch :::tip When to Use Default Values Perfect for information you already know: - Shipping addresses for order forms - Client contact details from your records - Company information for regular business partners - Any information recipients can review and correct if needed ::: ### How to Set Default Values You can set default values for each recipient to pre-fill common information. :::note Field Types Without Defaults **Signature**, **Initials**, and **Date** fields cannot have default values because they must be completed by the recipient at signing time. ::: ### Setting Default Values Per Recipient Default values are set per recipient and will pre-fill their fields. **Step 1: Find the Default Values Accordion** Scroll down in the right sidebar until you see the **"Default values for [recipient name] (Optional)"** accordion. Click to expand it. **Step 2: Enter Default Values** Fill in any of the available fields: - **Full name** - Complete name (e.g., "John Smith") - **First Name** - Given name only (e.g., "John") - **Last Name** - Family name only (e.g., "Smith") - **Email Address** - Contact email (e.g., "john.smith@company.com") - **Title** - Job title (e.g., "Senior Manager") - **Company** - Organization name (e.g., "Acme Corporation") **Step 3: Switch Between Recipients** Use the recipient tabs at the top to set different default values for each signer. :::tip Character Limits Each field has a maximum character limit. TurboSign will show you a character counter when you're approaching the limit so you don't lose any information. ::: ### Practical Examples **Example: Service Order** You're sending a service agreement to a client named Sarah Johnson. You already have her information from a previous order: **Default values for Sarah:** - Full name: "Sarah Johnson" - Email Address: "sarah.johnson@example.com" - Company: "Acme Corporation" - Text field for "Service Address": "123 Main Street, Suite 400, San Francisco, CA 94105" Now when Sarah receives the document, these fields will be pre-filled with this information. She can review and correct anything if needed before signing. ### Best Practices for Default Values **Do:** - ✅ **Use accurate information** - Double-check spelling and formatting - ✅ **Keep it concise** - Stay within character limits - ✅ **Save time on repeat signers** - Great for clients with existing records - ✅ **Pre-fill known facts** - Addresses, contact details, company names **Don't:** - ❌ **Don't force information** - Recipients can edit defaults if needed - ❌ **Don't use for signatures/initials** - These fields must be completed during signing - ❌ **Don't include sensitive data** - Avoid passwords, SSNs, or confidential information - ❌ **Don't overfill** - Only set defaults for information you truly know :::tip Time-Saving Tip If you send the same type of document to the same people regularly, setting default values can cut signing time in half! Recipients can review and submit much faster when common fields are already filled. ::: ## Step 8: Send the Document The final step! Once all fields are placed and everything looks perfect, it's time to send your document for signatures. **Click "Send Document"** in the **top right corner** to initiate the signing process. ![Send Document button in top right corner](/img/turbosign/ClickSendDocument.png) ### What Happens Next After clicking "Send Document": 1. **Email notifications** are sent to all recipients 2. **Recipients receive** a secure link to sign the document 3. **You get notifications** as each person signs 4. **Everyone receives** a copy of the fully signed document ## Congratulations! 🎉 **That's it!** Your document is now on its way to being signed. You've successfully: - ✅ **Uploaded or selected** your document - ✅ **Added recipients** and set signing order - ✅ **Placed signature fields** exactly where needed - ✅ **Sent the document** for signatures ### What to Expect **Timeline:** - Recipients usually receive emails within minutes - Most people sign within 24-48 hours - You'll get notifications for each signature **Tracking:** - Monitor progress in your TurboSign dashboard - Send gentle reminders if needed - Download the completed document when all signatures are collected ## Tips for Success ### For Faster Signatures - **Use clear document names** so recipients know what they're signing - **Add helpful descriptions** to provide context - **Send during business hours** for faster response - **Follow up politely** if signatures are delayed ### For Professional Results - **Test your document** by sending it to yourself first - **Use consistent field sizes** for a clean look - **Place fields logically** following document flow - **Keep signing order simple** unless legally required ### For Peace of Mind - **Double-check email addresses** before sending - **Review field placement** carefully - **Save templates** for documents you use frequently - **Keep records** of all signed documents ## Troubleshooting Common Issues ### "The Send Button is Disabled" **Solution:** Make sure you've placed at least one signature field for each recipient. The Send button appears only when your document is ready. ### "Recipient Says They Can't Haven't Received the Email" **Solution:** Check that you used the correct email address and that the signing link hasn't expired. You can resend the invitation from your dashboard. ### "I Made a Mistake in the Document" **Solution:** It's best practice to void the document and send a new version. ## What's Next? Now that you know how to send documents for signature, you might want to explore: - **Creating signature templates** for frequently used documents - **Setting up automated workflows** for recurring signature processes - **Integrating TurboSign** with your existing document workflows - **Managing completed documents** and signatures :::tip Final Thoughts You've just mastered the art of digital signatures! Every document you send with TurboSign saves time, reduces errors, and provides a professional experience for all parties involved. Welcome to the future of document signing! 🚀 ::: --- *Ready to sign your next document? Go forth and get those signatures! 📝* --- ## TurboSign Webhooks # Webhooks Webhooks enable your application to receive real-time notifications when important events occur in TurboSign. Instead of polling for changes, webhooks push event data to your specified endpoints immediately when signature documents are completed or voided. ![Get It Signed button on TurboDocx homepage](/img/webhooks/webhook-schema.png) ## Overview TurboSign webhooks provide a robust and secure way to integrate document signature events into your existing workflows. When configured, webhooks will automatically send HTTP POST requests to your specified URLs whenever subscribed events occur. ### Key Features - **Real-time Notifications**: Receive instant updates when documents are signed or voided - **Multiple URLs**: Configure up to 5 webhook URLs per configuration - **Secure Authentication**: HMAC-SHA256 signature verification ensures webhook authenticity - **Reliable Delivery**: Automatic retry logic with up to 3 attempts per webhook - **Delivery History**: Track and replay webhook deliveries with detailed logs - **Event Filtering**: Subscribe only to the events you need ## Configuration ### Setting Up Webhooks Webhooks can be configured through the TurboSign interface in your organization settings. 1. **Go to the Turbodocx Home Page and click on settings** - click on the settings on the sidemenu ![Get It Signed button on TurboDocx homepage](/img/webhooks/home-page.png) 2. **Navigate to Organization Settings** - Select "Organization Settings" from the tabs ![Get It Signed button on TurboDocx homepage](/img/webhooks/organization-setting.png) 3. **Scroll Down to Signature Configuration** - click "Configure Webhooks" ![Get It Signed button on TurboDocx homepage](/img/webhooks/core-features-section.png) 4. **Add Webhook URLs** - Enter your webhook endpoint URL(s) - You can add up to 5 different URLs - Each URL will receive all subscribed events - URLs must use HTTPS for production environments ![Get It Signed button on TurboDocx homepage](/img/webhooks/signature-webhook-config.png) 5. **Select Events to Subscribe** - Choose which events should trigger webhooks: - **Signature Document Completed**: Triggered when all signers have completed signing - **Signature Document Voided**: Triggered when a document is voided/cancelled ![Get It Signed button on TurboDocx homepage](/img/webhooks/signature-webhook-config.png) 6. **Save Configuration** - Click "Save Configuration" to activate your webhooks - Your webhook secret key will be displayed (only shown once for new configurations) - **Important**: Copy and securely store your webhook secret - it won't be shown again ![Get It Signed button on TurboDocx homepage](/img/webhooks/dill-signature-webhook-config.png) ![Get It Signed button on TurboDocx homepage](/img/webhooks/copy-webhook-secret.png) ### Managing Webhook Configuration #### Viewing Delivery History The Delivery History tab shows all webhook delivery attempts with detailed information: - **Event Type**: The type of event that triggered the webhook - **HTTP Status**: Response status code from your endpoint - **Attempts**: Number of delivery attempts made - **Timestamps**: When the webhook was created and last updated - **Actions**: View details or replay failed deliveries ![Get It Signed button on TurboDocx homepage](/img/webhooks/delivery-history.png) #### Webhook Secret Management Your webhook secret is used to verify that webhooks are genuinely from TurboDocx: - **Initial Generation**: A secret is automatically generated when you create a webhook configuration - **Security**: The secret is only shown in full immediately after generation or regeneration - **Regeneration**: You can regenerate the secret at any time if compromised - **Display**: After initial viewing, only a masked version (first 3 + \*\*\* + last 3 characters) is shown ![Get It Signed button on TurboDocx homepage](/img/webhooks/regenerate-secret.png) ## Webhook Events ### Signature Document Completed Triggered when all required signers have successfully signed a document. **Event Name**: `signature.document.completed` **Payload Example**: ```json { "event": "signature.document.completed", "event_id": "evt_01daa4ba531c42938f861c5a9ce9a5f2", "created_at": "2025-08-26T11:44:30.305Z", "version": "1.0", "data": { "document_id": "2dea093d-c38f-4898-b440-43dd9a14cd9d", "title": "Document Name", "status": "completed", "status_enum": "SignatureDocumentStatus.COMPLETED", "completed_at": "2025-08-26T11:44:30.299Z", "document_hash": "f516c4b9de36a5c9a999ba87abbc93078fdd0c9f6b855590d883d8bfb143308f" } } ``` ### Signature Document Voided Triggered when a document is voided or cancelled. **Event Name**: `signature.document.voided` **Payload Example**: ```json { "event": "signature.document.voided", "event_id": "evt_c825f202658b41ea932871ba13cc52a5", "created_at": "2025-08-26T11:42:03.622Z", "version": "1.0", "data": { "document_id": "9eee553b-28b6-4b43-b52b-4ef9957cc503", "title": "Statement of Work Example Draft", "status": "voided", "status_enum": "SignatureDocumentStatus.VOIDED", "voided_at": "2025-08-26T11:42:03.582Z", "void_reason": "signature not required", "document_hash": "b19151b93aed4f8cbcf060030a338dd414c249914eb8d2591c72390a0fa1b754" } } ``` ### Payload Fields | Field | Type | Description | | -------------------- | ------ | -------------------------------------------------------- | | `event` | string | The type of event (e.g., `signature.document.completed`) | | `event_id` | string | Unique identifier for this event instance | | `created_at` | string | ISO 8601 timestamp when the event occurred | | `version` | string | Webhook payload version (currently "1.0") | | `data.document_id` | string | Unique identifier of the signature document | | `data.title` | string | Document title/name | | `data.status` | string | Human-readable status | | `data.status_enum` | string | Programmatic status enum value | | `data.document_hash` | string | Document content hash for integrity verification | | `data.completed_at` | string | When the document was completed (completed event only) | | `data.voided_at` | string | When the document was voided (voided event only) | | `data.void_reason` | string | Reason for voiding (voided event only) | ## Signature Verification Every webhook request includes an `x-turbodocx-signature` header that you should verify to ensure the webhook is genuinely from TurboDocx. ### How It Works 1. TurboDocx creates a signature using HMAC-SHA256 2. The signature is computed from: `timestamp + "." + request_body` 3. The signature is sent in the `x-turbodocx-signature` header 4. Your endpoint verifies this signature using your webhook secret ### Verification Headers Each webhook request includes these headers: | Header | Description | | ------------------------- | -------------------------------------------------------- | | `X-TurboDocx-Signature` | HMAC signature for verification (format: `sha256=`) | | `X-TurboDocx-Timestamp` | Unix timestamp when the webhook was sent | | `X-TurboDocx-Event` | The event type that triggered this webhook | | `X-TurboDocx-Delivery-Id` | Unique ID for this delivery attempt (for idempotency) | ### Try it Now ### SDK Verification Examples Our SDKs include built-in webhook verification. The snippets below show the minimum receiver code; for the full SDK reference (createWebhook, updateWebhook, testWebhook, listWebhookDeliveries, replayWebhookDelivery, rotateSecret, framework integration patterns, and gotchas), see the dedicated TurboWebhooks SDK pages: - [TurboWebhooks JavaScript / TypeScript SDK](/docs/SDKs/webhooks-javascript) - [TurboWebhooks Python SDK](/docs/SDKs/webhooks-python) - [TurboWebhooks PHP SDK](/docs/SDKs/webhooks-php) - [TurboWebhooks Go SDK](/docs/SDKs/webhooks-go) - [TurboWebhooks Java SDK](/docs/SDKs/webhooks-java) Here are examples for each language: ```typescript const app = express(); app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const isValid = verifyWebhookSignature({ signature: req.headers['x-turbodocx-signature'], timestamp: req.headers['x-turbodocx-timestamp'], body: req.body, secret: process.env.TURBODOCX_WEBHOOK_SECRET }); if (!isValid) { return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body.toString()); // Process event... res.status(200).json({ received: true }); }); ``` ```python from turbodocx import verify_webhook_signature from fastapi import FastAPI, Request, HTTPException app = FastAPI() @app.post("/webhook") async def handle_webhook(request: Request): body = await request.body() is_valid = verify_webhook_signature( signature=request.headers.get("x-turbodocx-signature"), timestamp=request.headers.get("x-turbodocx-timestamp"), body=body, secret=os.environ["TURBODOCX_WEBHOOK_SECRET"] ) if not is_valid: raise HTTPException(status_code=401, detail="Invalid signature") event = json.loads(body) # Process event... return {"received": True} ``` ```go func webhookHandler(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) isValid := sdk.VerifyWebhookSignature( r.Header.Get("X-TurboDocx-Signature"), r.Header.Get("X-TurboDocx-Timestamp"), body, os.Getenv("TURBODOCX_WEBHOOK_SECRET"), ) if !isValid { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } var event sdk.WebhookEvent json.Unmarshal(body, &event) // Process event... w.WriteHeader(http.StatusOK) } ``` ```csharp [HttpPost] public async Task HandleWebhook() { using var reader = new StreamReader(Request.Body); var body = await reader.ReadToEndAsync(); var isValid = WebhookVerifier.VerifySignature( Request.Headers["X-TurboDocx-Signature"], Request.Headers["X-TurboDocx-Timestamp"], body, _configuration["TurboDocx:WebhookSecret"] ); if (!isValid) return Unauthorized("Invalid signature"); var webhookEvent = JsonSerializer.Deserialize(body); // Process event... return Ok(new { Received = true }); } ``` ```ruby def turbodocx_webhook body = request.raw_post is_valid = TurboDocx::WebhookVerifier.verify( signature: request.headers['X-TurboDocx-Signature'], timestamp: request.headers['X-TurboDocx-Timestamp'], body: body, secret: ENV['TURBODOCX_WEBHOOK_SECRET'] ) unless is_valid return render json: { error: 'Invalid signature' }, status: :unauthorized end event = JSON.parse(body) # Process event... render json: { received: true } end ``` **Go deeper:** the [TurboWebhooks SDK pages](/docs/SDKs#turbowebhooks-sdks) cover the full management surface (create/update/delete the webhook, test-fire it, rotate the secret, list and replay delivery history) and per-framework receiver patterns (Express, FastAPI, Spring Boot, net/http, Gin, Laravel, …). ### Security Best Practices 1. **Always verify signatures**: Never process webhooks without verifying the signature 2. **Use HTTPS**: Always use HTTPS endpoints in production 3. **Store secrets securely**: Keep webhook secrets in environment variables or secure vaults 4. **Implement timestamp validation**: Reject webhooks with timestamps older than 5 minutes 5. **Use timing-safe comparison**: Prevent timing attacks when comparing signatures 6. **Handle retries idempotently**: Use the `X-TurboDocx-Delivery-Id` to prevent duplicate processing 7. **Respond quickly**: Return 200 OK immediately and process webhooks asynchronously 8. **Log failures**: Keep logs of signature verification failures for security monitoring ## Delivery & Retries ### Delivery Behavior - **Timeout**: Each delivery attempt has a 10-second timeout - **Success Criteria**: Only HTTP 2xx status codes are considered successful - **Retry Logic**: Failed deliveries are automatically retried up to 3 times - **Retry Schedule**: Exponential backoff between retry attempts - **Delivery Order**: Webhooks are delivered to all configured URLs in parallel ### Handling Failures When a webhook delivery fails: 1. **Automatic Retries**: The system will automatically retry failed deliveries 2. **Delivery History**: All attempts are logged in the delivery history 3. **Manual Replay**: You can manually replay failed deliveries from the UI 4. **Error Details**: Response status codes and error messages are captured ### Best Practices for Your Endpoint 1. **Return 200 OK quickly**: Process webhooks asynchronously to avoid timeouts 2. **Implement idempotency**: Handle duplicate deliveries gracefully 3. **Queue for processing**: Use a message queue for reliable processing 4. **Monitor your endpoint**: Set up alerting for webhook processing failures 5. **Handle all event types**: Be prepared for new event types in the future ## Testing Webhooks ### Using the Test Feature You can test your webhook configuration before going live: 1. **Configure your webhook** with your test endpoint URL 2. **Save the configuration** to activate it 3. **Create a test signature document** and complete the signing process 4. **Check the Delivery History** to verify successful delivery 5. **Verify your endpoint** received and processed the webhook correctly ### Development Tools For local development, consider using: - **ngrok**: Expose your local server to receive webhooks - **Webhook.site**: Test webhook payloads without writing code - **RequestBin**: Inspect webhook requests in real-time - **Postman**: Simulate webhook requests for testing ### Testing Checklist - [ ] Webhook endpoint returns 200 OK status - [ ] Signature verification is working correctly - [ ] Timestamp validation is implemented - [ ] All event types are handled - [ ] Error handling is in place - [ ] Retry logic is handled idempotently - [ ] Logs capture webhook processing details - [ ] Performance under load has been tested ## Troubleshooting ### Common Issues #### Webhook Not Receiving Events **Symptoms**: Events occur but webhooks aren't triggered **Solutions**: - Verify webhook configuration is saved and active - Check that you've subscribed to the correct events - Ensure your endpoint URL is correct and accessible - Review the Delivery History for error messages #### Signature Verification Failing **Symptoms**: 401 Unauthorized responses from your endpoint **Solutions**: - Ensure you're using the raw request body (not parsed JSON) - Verify the webhook secret matches exactly - Check that header names are lowercase in your code - Confirm timestamp validation isn't too strict #### Timeouts **Symptoms**: Webhook deliveries show timeout errors **Solutions**: - Return 200 OK immediately, process asynchronously - Optimize endpoint performance - Check network connectivity and firewall rules - Consider increasing server resources #### Duplicate Deliveries **Symptoms**: Same event processed multiple times **Solutions**: - Implement idempotency using `X-TurboDocx-Delivery-Id` - Store processed event IDs temporarily - Use database constraints to prevent duplicates ### Getting Help If you encounter issues not covered here: 1. **Check the Delivery History** for detailed error messages 2. **Review your endpoint logs** for processing errors 3. **Test with a simple endpoint** to isolate issues 4. **Contact Support** with your webhook configuration details and error messages ## API Reference ### Webhook Object ```json { "id": "webhook_abc123", "orgId": "org_xyz789", "name": "signature", "urls": [ "https://api.example.com/webhooks/turbosign", "https://backup.example.com/webhooks" ], "events": ["signature.document.completed", "signature.document.voided"], "secretExists": true, "maskedSecret": "whs***f6a", "isActive": true, "createdOn": "2024-01-15T09:00:00.000Z", "updatedOn": "2024-01-15T09:00:00.000Z" } ``` ### Delivery Object ```json { "id": "delivery_def456", "webhookId": "webhook_abc123", "eventType": "signature.document.completed", "url": "https://api.example.com/webhooks/turbosign", "httpStatus": 200, "attemptCount": 1, "maxAttempts": 3, "isDelivered": true, "deliveredAt": "2024-01-15T10:30:05.000Z", "createdOn": "2024-01-15T10:30:00.000Z", "updatedOn": "2024-01-15T10:30:05.000Z" } ``` --- ## Next Steps - [Learn about TurboSign](/docs/TurboSign/Setting%20up%20TurboSign) - [Explore API Documentation](/docs/API/turbodocx-api-documentation) - [View Integration Guides](/docs/Integrations) --- ## Welcome # Welcome to TurboDocx ![TurboDocx welcome banner for document automation platform](/img/welcome_to_dev-docs/Welcome.png) ## Are You Ready to Unlock the Power of Document Automation? Use our Documentation located on the left side of this screen to learn how to get started!