diff --git a/docs/SDKs/agent-skills.md b/docs/SDKs/agent-skills.md
index d09b37c..8b844f1 100644
--- a/docs/SDKs/agent-skills.md
+++ b/docs/SDKs/agent-skills.md
@@ -2,7 +2,7 @@
title: Install with AI Agents (Agent Skills)
sidebar_position: 0
sidebar_label: Install with AI Agents
-description: Install the TurboDocx SDK and @turbodocx/html-to-docx into any project in one prompt using the TurboDocx Agent Skill — works with Claude Code, GitHub Copilot, Cursor, OpenCode, OpenAI Codex CLI, and Gemini CLI.
+description: "TurboDocx Agent Skill: install the SDK and html-to-docx in one prompt via Claude Code, Copilot, Cursor, or Codex CLI."
keywords:
- agent skills
- ai agent
diff --git a/docs/SDKs/deliverable-go.md b/docs/SDKs/deliverable-go.md
index 13f61d3..ea99a08 100644
--- a/docs/SDKs/deliverable-go.md
+++ b/docs/SDKs/deliverable-go.md
@@ -70,7 +70,7 @@ func main() {
```
:::tip No SenderEmail Required
-Use `NewDeliverableClientOnly()` when you only need document generation — it skips the `SenderEmail` validation required by TurboSign.
+Use `NewDeliverableClientOnly()` when you only need document generation: it skips the `SenderEmail` validation required by TurboSign.
:::
### Environment Variables
@@ -422,20 +422,7 @@ if err != nil {
## 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 |
+`GenerateDeliverable` returns `NotFoundError` when `TemplateID` doesn't match a template in the org, and `ValidationError` for invalid request parameters, most commonly a `DeliverableVariable` missing `Text` (required unless it sets `VariableStack` or `IsDisabled: true`) or specifying an unsupported `MimeType`. Match on the concrete type with `errors.As`, same as every other Go SDK call:
### Handling Errors
@@ -479,13 +466,7 @@ if err != nil {
}
```
-### Error Properties
-
-| Property | Type | Description |
-| ------------ | -------- | ---------------------------- |
-| `Message` | `string` | Human-readable error message |
-| `StatusCode` | `int` | HTTP status code |
-| `Code` | `string` | Error code (if available) |
+The full typed-error table (`AuthenticationError`, `AuthorizationError`, `ConflictError`, `RateLimitError`, `NetworkError`, HTTP status mapping) and the `Message`/`StatusCode`/`Code` fields on every error are documented once in the [Go SDK's Error Handling reference](./go.md#error-handling).
---
diff --git a/docs/SDKs/deliverable-java.md b/docs/SDKs/deliverable-java.md
index 9df5d27..9a0bbf3 100644
--- a/docs/SDKs/deliverable-java.md
+++ b/docs/SDKs/deliverable-java.md
@@ -32,7 +32,7 @@ The official TurboDocx Deliverable SDK for Java applications. Generate documents
com.turbodocx
turbodocx-sdk
- 0.5.0
+ 0.7.0
```
@@ -40,14 +40,14 @@ The official TurboDocx Deliverable SDK for Java applications. Generate documents
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.5.0")
+implementation("com.turbodocx:turbodocx-sdk:0.7.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.5.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
```
@@ -87,7 +87,7 @@ public class Main {
```
:::tip No senderEmail Required
-Use `buildDeliverableClient()` when you only need document generation — it skips the `senderEmail` validation required by TurboSign.
+Use `buildDeliverableClient()` when you only need document generation: it skips the `senderEmail` validation required by TurboSign.
:::
### Environment Variables
@@ -285,9 +285,9 @@ The builder authenticates with either `apiKey(...)` or `accessToken(...)` (a bea
| Builder method | Returns | Use for |
| -------------------------- | ------------------- | --------------------------------------------------- |
-| `build()` | `TurboDocxClient` | Full client — `turboSign()` and `deliverable()` |
+| `build()` | `TurboDocxClient` | Full client, `turboSign()` and `deliverable()` |
| `buildDeliverableClient()` | `DeliverableClient` | Document generation only (no `senderEmail` needed) |
-| `buildWebhooksClient()` | `TurboWebhooks` | Signature webhook subscriptions — see [TurboWebhooks Java SDK](/docs/SDKs/webhooks-java) |
+| `buildWebhooksClient()` | `TurboWebhooks` | Signature webhook subscriptions, see [TurboWebhooks Java SDK](/docs/SDKs/webhooks-java) |
```java
// Authenticate with a bearer access token instead of an API key
@@ -400,20 +400,7 @@ 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 |
+`deliverable.generateDeliverable()` throws `TurboDocxException.NotFoundException` when `templateId` doesn't match a template in the org, and `TurboDocxException.ValidationException` when a variable in the request is missing a required field:
### Handling Errors
@@ -443,13 +430,7 @@ try {
}
```
-### Error Properties
-
-| Property | Type | Description |
-| ----------------- | -------- | ---------------------------- |
-| `getMessage()` | `String` | Human-readable error message |
-| `getStatusCode()` | `int` | HTTP status code |
-| `getCode()` | `String` | Error code (if available) |
+The full typed-exception table (`AuthenticationException`, `AuthorizationException`, `ConflictException`, `RateLimitException`, `NetworkException`, HTTP status mapping) and the `getMessage()`/`getStatusCode()`/`getCode()` methods shared by every exception are documented once in the [Java SDK's Error Handling reference](./java.md#error-handling).
---
diff --git a/docs/SDKs/deliverable-javascript.md b/docs/SDKs/deliverable-javascript.md
index 0f85628..11a8b5d 100644
--- a/docs/SDKs/deliverable-javascript.md
+++ b/docs/SDKs/deliverable-javascript.md
@@ -99,14 +99,14 @@ Deliverable.configure({
| Property | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------------------------------ |
| `apiKey` | `string` | Yes\* | Your TurboDocx API key |
-| `accessToken` | `string` | Yes\* | OAuth access token — alternative to `apiKey` |
+| `accessToken` | `string` | Yes\* | OAuth access token, alternative to `apiKey` |
| `orgId` | `string` | Yes | Your organization ID |
| `baseUrl` | `string` | No | API base URL (defaults to `https://api.turbodocx.com`) |
\*Supply either `apiKey` or `accessToken`. When both are set, `accessToken` wins.
:::tip No Sender Email Required
-Unlike TurboSign, the Deliverable module only requires a credential and `orgId` — no sender email or name is needed.
+Unlike TurboSign, the Deliverable module only requires a credential and `orgId`: no sender email or name is needed.
:::
### Environment Variables
@@ -602,20 +602,7 @@ 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 |
+`Deliverable.generateDeliverable()` rejects with `NotFoundError` when `templateId` doesn't match a template in the org, and `ValidationError` when an entry in `variables` is missing a required field. Both extend the base `TurboDocxError` class:
### Handling Errors
@@ -710,15 +697,7 @@ try {
-### 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 |
+The full typed-error table (`AuthenticationError`, `AuthorizationError`, `ConflictError`, `RateLimitError`, `NetworkError`, HTTP status and code mapping) and the `message`/`statusCode`/`code` properties shared by every error are documented once in the [JavaScript / TypeScript SDK's Error Handling reference](./javascript.md#error-handling).
---
diff --git a/docs/SDKs/deliverable-php.md b/docs/SDKs/deliverable-php.md
index d93524b..9fa8eb7 100644
--- a/docs/SDKs/deliverable-php.md
+++ b/docs/SDKs/deliverable-php.md
@@ -81,7 +81,7 @@ Deliverable::configure(DeliverableConfig::fromEnvironment());
:::tip No senderEmail Required
-Unlike TurboSign, the Deliverable module only requires `apiKey` and `orgId` — no sender email or name is needed.
+Unlike TurboSign, the Deliverable module only requires `apiKey` and `orgId`: no sender email or name is needed.
:::
### Environment Variables
@@ -361,20 +361,7 @@ 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 |
+`Deliverable::generateDeliverable()` throws `NotFoundException` when `templateId` doesn't match a template in the org, and `ValidationException` when a variable in the `variables` array is missing a required field:
### Handling Errors
@@ -423,13 +410,7 @@ try {
}
```
-### 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')
+The full typed-exception table (`AuthenticationException`, `AuthorizationException`, `ConflictException`, `RateLimitException`, `NetworkException`, HTTP status mapping) and the `getMessage()`/`statusCode`/`errorCode` properties shared by every exception are documented once in the [PHP SDK's Error Handling reference](./php.md#error-handling).
---
diff --git a/docs/SDKs/deliverable-python.md b/docs/SDKs/deliverable-python.md
index 9b916d1..e21c413 100644
--- a/docs/SDKs/deliverable-python.md
+++ b/docs/SDKs/deliverable-python.md
@@ -70,7 +70,7 @@ Deliverable.configure(
```
:::tip No Sender Email Required
-Unlike TurboSign, the Deliverable module only requires `api_key` and `org_id` — no sender email or name is needed.
+Unlike TurboSign, the Deliverable module only requires `api_key` and `org_id`: no sender email or name is needed.
:::
### Environment Variables
@@ -349,20 +349,7 @@ with open("report.pdf", "wb") as f:
## 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 |
+`Deliverable.generate_deliverable()` raises `NotFoundError` when `template_id` doesn't match a template in the org, and `ValidationError` for invalid request parameters, most commonly a variable dict missing `text` (required unless it sets `variableStack` or `isDisabled: True`) or specifying an unsupported `mimeType`. Both extend the base `TurboDocxError`:
### Handling Errors
@@ -416,15 +403,7 @@ async def main():
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 |
+The full typed-error table (`AuthenticationError`, `AuthorizationError`, `ConflictError`, `RateLimitError`, `NetworkError`, HTTP status mapping) and the `message`/`status_code`/`code` attributes shared by every error are documented once in the [Python SDK's Error Handling reference](./python.md#error-handling).
---
diff --git a/docs/SDKs/go.md b/docs/SDKs/go.md
index 760124e..82e06fe 100644
--- a/docs/SDKs/go.md
+++ b/docs/SDKs/go.md
@@ -324,7 +324,7 @@ result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureReque
### Schedule reminders and expiration
-`SendSignature` accepts an optional `SignatureSchedule` that turns on automatic reminder emails and a signing deadline. Every field is a pointer, and **both features are off by default** — omit the schedule entirely to preserve the original send behavior. The resolved schedule is **frozen onto the document at send time**, so later changes to your org defaults never touch a document already out for signature.
+`SendSignature` accepts an optional `SignatureSchedule` that turns on automatic reminder emails and a signing deadline. Every field is a pointer, and **both features are off by default**: omit the schedule entirely to preserve the original send behavior. The resolved schedule is **frozen onto the document at send time**, so later changes to your org defaults never touch a document already out for signature.
```go
result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{
@@ -346,7 +346,7 @@ result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureReque
| `RemindersEnabled` | `*bool` | Master switch for automatic reminders. Default off. |
| `ReminderDelay` | `*Duration` | Time to the **first** reminder, measured from that signer's invitation. |
| `ReminderInterval` | `*Duration` | Gap between **subsequent** reminders. |
-| `MaxReminders` | `*int` | Automatic reminders per signer. Valid range **-1..50** — `-1` unlimited, `0` none, default `5`. |
+| `MaxReminders` | `*int` | Automatic reminders per signer. Valid range **-1..50**: `-1` unlimited, `0` none, default `5`. |
| `ExpirationEnabled` | `*bool` | Master switch for the signing deadline. Default off. |
| `ExpireAfter` | `*Duration` | How long the document stays signable, counted from sending. |
| `ExpirationWarning` | `*Duration` | How far **before** expiry warnings start. `0` = never warn. |
@@ -356,7 +356,7 @@ A `Duration` is a `{Value, Unit}` pair; `Unit` is `"hours"` or `"days"`. `Value`
### Get status
-Check the status of a document. The response includes `ExpiresAt` — the signing-window deadline as an ISO 8601 string, or `""` when expiration is off — and a `Status` that can reach the terminal value `expired` once the deadline passes. For per-signer detail, use [Get recipients](#get-recipients).
+Check the status of a document. The response includes `ExpiresAt` (the signing-window deadline as an ISO 8601 string, or `""` when expiration is off) and a `Status` that can reach the terminal value `expired` once the deadline passes. For per-signer detail, use [Get recipients](#get-recipients).
```go
status, err := client.TurboSign.GetStatus(ctx, "document-uuid")
@@ -392,7 +392,7 @@ for _, r := range progress.Recipients {
:::tip Two status fields, and they differ on purpose
`status` is the raw database value and is only ever `pending`, `viewed` or `completed`.
-`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired` — that
+`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired`: that
is the one to display.
On a voided or expired document an unsigned signer still reads `pending` in `status`, so
@@ -405,14 +405,14 @@ the document is terminal.
:::
-Each recipient also carries a `delivery` block — `firstSentOn`, `lastSentOn`, `totalSent`,
+Each recipient also carries a `delivery` block: `firstSentOn`, `lastSentOn`, `totalSent`,
`reminderCount`, `lastRemindedAt`, `warningCount`, `lastWarningAt`. It counts the signature
request, resends, reminders, expiry warnings and terminal notices; CC notifications are
excluded, since a CC address is not a signer.
:::warning `reminderCount` and `lastRemindedAt` do not mean what their names suggest
-`reminderCount` counts **automatic (scheduled) reminders only** — the counter `maxReminders`
+`reminderCount` counts **automatic (scheduled) reminders only**: the counter `maxReminders`
caps. A manual "remind now" is a standalone nudge that must not consume the cap budget, so it
does **not** increment this, even though the email it sends *does* appear in `totalSent`.
@@ -421,7 +421,7 @@ signature-request send, each scheduled reminder, each manual "remind now" and ea
warning all stamp it. Only scheduled reminders bump `reminderCount`.
So a freshly-sent document returns a non-null `lastRemindedAt` equal to the invitation
-timestamp alongside `reminderCount: 0` — nobody has been reminded. To answer "have we actually
+timestamp alongside `reminderCount: 0`: nobody has been reminded. To answer "have we actually
chased this person", read `totalSent`, not `reminderCount`.
`warningCount` / `lastWarningAt` have no such caveat.
@@ -477,7 +477,7 @@ result, err := client.TurboSign.ResendEmail(ctx, "document-uuid", []string{"reci
### Send reminder
-Send a standalone reminder to whoever's turn it is to sign (`POST /turbosign/documents/:id/send-reminder`). It is independent of the automatic reminder cadence — it works even when reminders are disabled or the per-signer `MaxReminders` cap is already spent, does **not** consume that cap, and only emails signers at the **current** signing order. Pass `nil` for `recipientIDs` to remind everyone eligible; do **not** pass an empty slice, which the API rejects.
+Send a standalone reminder to whoever's turn it is to sign (`POST /turbosign/documents/:id/send-reminder`). It is independent of the automatic reminder cadence: it works even when reminders are disabled or the per-signer `MaxReminders` cap is already spent, does **not** consume that cap, and only emails signers at the **current** signing order. Pass `nil` for `recipientIDs` to remind everyone eligible; do **not** pass an empty slice, which the API rejects.
```go
resp, err := client.TurboSign.SendReminder(ctx, "document-uuid", nil)
@@ -497,7 +497,7 @@ This differs from **Resend**: resend re-sends the original invitation email, whi
## Error Handling
-The SDK provides typed errors for different error scenarios:
+Every typed error embeds `TurboDocxError` by value, which promotes its `Message string`, `StatusCode int`, and `Code string` fields onto the typed error, so read them directly off the matched variable (`authErr.Message`, not a getter). Match with `errors.As`, as the example below does:
### Error Types
@@ -508,16 +508,17 @@ The SDK provides typed errors for different error scenarios:
| `AuthorizationError` | 403 | Authenticated but lacks required permissions |
| `ValidationError` | 400 | Invalid request parameters |
| `NotFoundError` | 404 | Resource not found |
+| `ConflictError` | 409 | Request conflicts with current resource state; most common on the webhook routes (creating or renaming to a name that already exists) |
| `RateLimitError` | 429 | Too many requests |
| `NetworkError` | - | Network connectivity issues |
### Error Properties
| Property | Type | Description |
-| ------------ | -------- | ---------------------------- |
-| `Message` | `string` | Human-readable error message |
+| ------------ | -------- | ----------------------------- |
+| `Message` | `string` | Human-readable error message. `Error()` does not return this bare string: it returns `TurboDocx API error [CODE]: MESSAGE (status N)`, omitting the `[CODE]` segment when `Code` is empty. Compare against `.Message` directly, not `err.Error()` |
| `StatusCode` | `int` | HTTP status code |
-| `Code` | `string` | Error code (if available) |
+| `Code` | `string` | Machine-readable code; the API's code wins when present, otherwise the SDK fills in a per-status default for each of the 7 named types above. The bare `TurboDocxError` returned for an unmapped status (e.g. an unexpected 5xx) can have an empty `Code` if the API didn't supply one |
### Example
@@ -535,6 +536,7 @@ if err != nil {
var authzErr *turbodocx.AuthorizationError
var validationErr *turbodocx.ValidationError
var notFoundErr *turbodocx.NotFoundError
+ var conflictErr *turbodocx.ConflictError
var rateLimitErr *turbodocx.RateLimitError
var networkErr *turbodocx.NetworkError
@@ -547,6 +549,8 @@ if err != nil {
log.Printf("Validation error: %s", validationErr.Message)
case errors.As(err, ¬FoundErr):
log.Printf("Not found: %s", notFoundErr.Message)
+ case errors.As(err, &conflictErr):
+ log.Printf("Conflict: %s", conflictErr.Message)
case errors.As(err, &rateLimitErr):
log.Printf("Rate limited: %s", rateLimitErr.Message)
case errors.As(err, &networkErr):
@@ -610,7 +614,7 @@ The `Type` field accepts the following string values:
| `Required` | `bool` | No | Make field required |
| `BackgroundColor` | `string` | No | Background color |
| `Template` | `*TemplateAnchor` | No | Template anchor configuration |
-| `Metadata` | `*FieldMetadata` | No | Conditional (IF/THEN) metadata — see below |
+| `Metadata` | `*FieldMetadata` | No | Conditional (IF/THEN) metadata, see below |
\*Required when not using template anchors
@@ -711,5 +715,5 @@ For detailed information about advanced configuration and API concepts, see:
## Resources
- [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/go-sdk)
-- [API Reference](/docs/TurboSign/API-Signatures)
+- [API Reference](/docs/TurboSign/API%20Signatures)
- [Webhook Configuration](/docs/TurboSign/Webhooks)
diff --git a/docs/SDKs/index.md b/docs/SDKs/index.md
index cbc208b..94238cd 100644
--- a/docs/SDKs/index.md
+++ b/docs/SDKs/index.md
@@ -25,7 +25,7 @@ Official client libraries for the TurboDocx API. Build document generation, digi
## Choose Your Product
-All five modules ship in the **same package** for each language — pick the one that matches what you're building:
+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… |
| :------------- | :----------------------------------------------------------------------------------------- |
@@ -37,7 +37,7 @@ All five modules ship in the **same package** for each language — pick the one
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.
:::tip Install with one prompt
-Skip the boilerplate — use the [TurboDocx Agent Skill](./agent-skills.md) to install the SDK, configure environment variables, and generate working integration code via Claude Code, GitHub Copilot, Cursor, OpenCode, Codex CLI, or Gemini CLI:
+Skip the boilerplate: use the [TurboDocx Agent Skill](./agent-skills.md) to install the SDK, configure environment variables, and generate working integration code via Claude Code, GitHub Copilot, Cursor, OpenCode, Codex CLI, or Gemini CLI:
```bash
npx skills add TurboDocx/quickstart
@@ -58,7 +58,7 @@ Send documents for legally-binding eSignatures with full audit trails.
## TurboWebhooks SDKs
-Subscribe to all 7 TurboSign signature events — `sent`, `viewed`, `recipient_signed`, `signed`, `completed`, `finalization_failed`, `voided` — and verify inbound signatures with HMAC-SHA256. Each SDK exports the full set as constants, so you never hand-write the wire strings.
+Subscribe to all 7 TurboSign signature events (`sent`, `viewed`, `recipient_signed`, `signed`, `completed`, `finalization_failed`, `voided`) and verify inbound signatures with HMAC-SHA256. Each SDK exports the full set as constants, so you never hand-write the wire strings.
| Language | Package | Install Command | Links |
| :------------------------ | :-------------- | :---------------------------- | :----------------------------------------------------------------------------------------------------- |
@@ -111,20 +111,20 @@ 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). It is a **per-request body field on every signature request** and 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 and TurboWebhooks do not use it at all.
+:::note senderEmail for TurboSign
+TurboSign accepts a `senderEmail` (used as the reply-to address for signature request emails). The **JS/TS SDK enforces it client-side**: `TurboSign.configure()` throws a `ValidationError` (generic `VALIDATION_ERROR` code, not an API error code) if no `senderEmail` is supplied in configuration or via the `TURBODOCX_SENDER_EMAIL` environment variable; this check runs once at configure time, not per request. Other SDKs may differ; check each SDK's README. The **backend API itself does not require `senderEmail`** for TurboSign or TurboQuote; a request or org template with no sender falls back to a generic TurboDocx no-reply address and name and is never rejected. Deliverable and TurboWebhooks do not use `senderEmail` at all.
-**TurboQuote is different:** there is **no `senderEmail` field on a quote request**, but a sender is still required. It is resolved from your organization's **quote template** (Quote Settings). An API-key caller whose template has no sender email gets `400 SenderEmailRequired` on create, duplicate, send, and handle-expired-sent — see [Prepared By & Sender Identity](/docs/TurboQuote/Prepared%20By%20and%20Sender%20Identity).
+**TurboQuote:** there is **no `senderEmail` field on a quote request**. A sender is resolved from your organization's **quote template** (Quote Settings) when set; if none is configured, the quote falls back to a generic TurboDocx sender rather than failing. See [Prepared By & Sender Identity](/docs/TurboQuote/Prepared%20By%20and%20Sender%20Identity).
:::
#### 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` | a **Sender Email + Sender Name on the org quote template** (no per-request sender field exists) |
-| **TurboWebhooks** | `TURBODOCX_API_KEY` (**administrator** role — non-admin keys get 403) | `TURBODOCX_ORG_ID` | the webhook secret returned by `createWebhook`, to verify inbound events |
+| **TurboSign** | `TURBODOCX_API_KEY` | `TURBODOCX_ORG_ID` | `TURBODOCX_SENDER_EMAIL` (required by the JS/TS SDK at configure time, reply-to for signer emails; the API itself falls back to a generic sender if omitted) |
+| **Deliverable** | `TURBODOCX_API_KEY` | `TURBODOCX_ORG_ID` | None |
+| **TurboQuote** | `TURBODOCX_API_KEY` | `TURBODOCX_ORG_ID` | a **Sender Email + Sender Name on the org quote template** recommended (no per-request sender field exists; falls back to a generic TurboDocx sender if not configured) |
+| **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
@@ -447,7 +447,7 @@ public class Main {
All TurboDocx SDKs provide access to:
-### TurboSign — Digital Signatures
+### TurboSign: Digital Signatures
Send documents for legally-binding eSignatures with full audit trails.
@@ -464,7 +464,7 @@ Send documents for legally-binding eSignatures with full audit trails.
[Learn more about TurboSign →](/docs/TurboSign/Setting%20up%20TurboSign)
-### Deliverable — Document Generation
+### Deliverable: Document Generation
Generate documents from templates with dynamic variable injection, download source files and PDFs.
@@ -480,7 +480,7 @@ Generate documents from templates with dynamic variable injection, download sour
[Learn more about Deliverable SDKs →](/docs/SDKs/deliverable-javascript)
-### TurboQuote — Sales Quoting & CPQ
+### TurboQuote: Sales Quoting & CPQ
Build quotes and proposals: line items, a product/bundle catalog, price books, companies, and contacts.
@@ -496,7 +496,7 @@ Build quotes and proposals: line items, a product/bundle catalog, price books, c
[Learn more about TurboQuote SDKs →](/docs/SDKs/quote-javascript)
-### TurboWebhooks — Signature Events
+### TurboWebhooks: Signature Events
Subscribe a per-org endpoint to TurboSign events and verify inbound deliveries with HMAC-SHA256. **Requires an administrator API key.**
@@ -508,7 +508,7 @@ Subscribe a per-org endpoint to TurboSign events and verify inbound deliveries w
| `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 |
+| `verifyWebhookSignature()` | Free function, verify the `X-TurboDocx-Signature` header on a received event |
[Learn more about TurboWebhooks SDKs →](/docs/SDKs/webhooks-javascript)
@@ -592,7 +592,8 @@ async def main():
try:
result = await TurboSign.send_signature(...)
except TurboDocxError as e:
- print(f"Error {e.code}: {e.message}")
+ # TurboDocxError doesn't set a .message attribute; str(e) is the message
+ print(f"Error {e.code}: {e}")
if e.code == "VALIDATION_ERROR":
# Handle validation error
pass
@@ -616,7 +617,7 @@ try {
echo "Validation error: {$e->getMessage()}\n";
// Handle validation error
} catch (TurboDocxException $e) {
- echo "Error {$e->getCode()}: {$e->getMessage()}\n";
+ echo "Error {$e->errorCode}: {$e->getMessage()}\n";
echo "Status code: {$e->statusCode}\n";
}
```
@@ -627,12 +628,12 @@ try {
```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
- }
+ // errors.As must target the specific type: a *TurboDocxError target does not match
+ // *ValidationError, *AuthenticationError, etc., even though each embeds TurboDocxError.
+ // See the Go SDK's own Error Handling reference for the full set of named types.
+ var validationErr *sdk.ValidationError
+ if errors.As(err, &validationErr) {
+ fmt.Printf("Validation error [%s]: %s\n", validationErr.Code, validationErr.Message)
}
}
```
@@ -641,12 +642,12 @@ if err != nil {
```java
-import com.turbodocx.sdk.TurboSign;
-import com.turbodocx.sdk.TurboDocxException;
-import com.turbodocx.sdk.TurboDocxException.*;
+import com.turbodocx.TurboDocxException;
+import com.turbodocx.TurboDocxException.*;
+import com.turbodocx.models.*;
try {
- SigningResult result = turboSign.sendSignature(/* ... */);
+ SendSignatureResponse result = client.turboSign().sendSignature(/* ... */);
} catch (AuthenticationException e) {
System.err.println("Invalid API key: " + e.getMessage());
} catch (ValidationException e) {
@@ -675,19 +676,18 @@ try {
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests, retry with backoff |
| `NETWORK_ERROR` | N/A | Network connection or timeout error |
-`code` is **always populated**. When the API returns a specific code the SDK surfaces it
-verbatim; otherwise it falls back to the class default above, so you can branch on `code`
-without a null check.
+When the API returns a specific code the SDK surfaces it verbatim; otherwise, for one of the
+7 named categories above, it falls back to that class's default, so `code` is populated for
+those without a null check. An error for a status code outside this table (an unexpected 5xx,
+for example) is not guaranteed a `code`.
### TurboQuote / TurboSign specific codes
These are returned by the API and passed through unchanged. They are more precise than the
-generic codes above — prefer them when handling a specific failure.
+generic codes above; prefer them when handling a specific failure.
| Code | HTTP Status | Meaning |
| :------------------------- | :---------- | :------------------------------------------------------------------------------------------ |
-| `SenderEmailRequired` | 400 | No sender email could be resolved. TurboSign: set `senderEmail` on the request. TurboQuote: configure one on the org quote template (Quote Settings). |
-| `SenderNameRequired` | 400 | No sender name could be resolved — the API key has no usable name. |
| `QuoteHasNoLineItems` | 400 | The quote has no line items. Add at least one product, bundle, or custom line item. |
| `QuoteExpired` | 400 | The quote is past its `validUntil` date. Update the date before sending. |
| `QuoteValidUntilRequired` | 400 | The quote has no `validUntil` date set. |
@@ -699,7 +699,7 @@ generic codes above — prefer them when handling a specific failure.
### Error messages carry the actionable reason
The API reports validation failures in several envelopes. The SDKs unwrap all of them, so
-`error.message` is the specific field-level reason — not a generic
+`error.message` is the specific field-level reason, not a generic
`"There was an issue validating the body"`. Multiple field errors are joined with `"; "`:
```
@@ -711,8 +711,8 @@ The API reports validation failures in several envelopes. The SDKs unwrap all of
## Audit Trail & Client Context
Every action you take through an SDK is recorded in the TurboDocx audit trail. All six SDKs
-automatically attach **client-context headers** to **every** request — including TurboSign,
-Deliverable, TurboQuote, TurboWebhooks, and TurboPartner — so the audit trail records real
+automatically attach **client-context headers** to **every** request, including TurboSign,
+Deliverable, TurboQuote, TurboWebhooks, and TurboPartner, so the audit trail records real
environment details instead of blanks:
| Recorded column | What the SDK sends |
@@ -723,7 +723,7 @@ environment details instead of blanks:
| **Language** | The machine's locale (e.g. `en-US`) |
| **Application** | `TurboDocx SDK ` |
-You do not configure any of this — it is collected and sent for you.
+You do not configure any of this: it is collected and sent for you.
### SDK / n8n calls vs. raw API calls
@@ -735,7 +735,7 @@ The audit trail distinguishes how a request reached TurboDocx:
| The TurboDocx n8n node | `TurboDocx n8n Node `, with real device, OS, timezone, and language |
| A raw HTTP/API call | The **name of the HTTP library** that made the call, the action `API Request`, and `N/A` for the environment fields it cannot know |
-Raw API calls show `N/A` — not `Unknown` — for the fields no client context was supplied for. If
+Raw API calls show `N/A` (not `Unknown`) for the fields no client context was supplied for. If
you want fully attributed audit entries, call through an SDK or the n8n node rather than hand-rolled
HTTP.
diff --git a/docs/SDKs/java.md b/docs/SDKs/java.md
index 9a9f782..2db1cd7 100644
--- a/docs/SDKs/java.md
+++ b/docs/SDKs/java.md
@@ -32,7 +32,7 @@ The official TurboDocx SDK for Java applications. Build document generation and
com.turbodocx
turbodocx-sdk
- 0.5.0
+ 0.7.0
```
@@ -40,14 +40,14 @@ The official TurboDocx SDK for Java applications. Build document generation and
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.5.0")
+implementation("com.turbodocx:turbodocx-sdk:0.7.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.5.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
```
@@ -97,8 +97,8 @@ public class Main {
| `senderName(String)` | `String` | No | - | Display name used on signature request emails |
| `baseUrl(String)` | `String` | No | `https://api.turbodocx.com` | API base URL |
| `connectTimeoutSeconds(int)` | `int` | No | `60` | Connection timeout |
-| `readTimeoutSeconds(int)` | `int` | No | `120` | Read timeout — raise it for large document uploads |
-| `writeTimeoutSeconds(int)` | `int` | No | `60` | Write timeout — raise it for large document uploads |
+| `readTimeoutSeconds(int)` | `int` | No | `120` | Read timeout, raise it for large document uploads |
+| `writeTimeoutSeconds(int)` | `int` | No | `60` | Write timeout, raise it for large document uploads |
\*Provide either `apiKey` or `accessToken`.
@@ -116,7 +116,7 @@ TurboDocxClient client = new TurboDocxClient.Builder()
### Closing the Client
-`TurboDocxClient` implements `AutoCloseable`. Calling `close()` shuts down the underlying OkHttp dispatcher and connection pool, so long-running JVM services should close clients they no longer need — use try-with-resources for short-lived clients:
+`TurboDocxClient` implements `AutoCloseable`. Calling `close()` shuts down the underlying OkHttp dispatcher and connection pool, so long-running JVM services should close clients they no longer need. Use try-with-resources for short-lived clients:
```java
try (TurboDocxClient client = new TurboDocxClient.Builder()
@@ -427,11 +427,11 @@ SendSignatureResponse result = client.turboSign().sendSignature(
);
```
-Each `Duration` is a `{value, unit}` pair where `unit` is `"hours"` or `"days"` and `value` is a whole number from **1 up to 999 days (23976 hours)**. `maxReminders` accepts **-1 to 50** (`-1` unlimited, `0` none, default `5`) and caps only automatic reminders — never expiry warnings; `expirationWarning` may be `0` to disable warnings. Reminders and expiry warnings run as two independent clocks, coordinated so a signer never receives both at the same moment — and a reminder cadence that would outlive the expiry window is rejected with `400`.
+Each `Duration` is a `{value, unit}` pair where `unit` is `"hours"` or `"days"` and `value` is a whole number from **1 up to 999 days (23976 hours)**. `maxReminders` accepts **-1 to 50** (`-1` unlimited, `0` none, default `5`) and caps only automatic reminders, never expiry warnings; `expirationWarning` may be `0` to disable warnings. Reminders and expiry warnings run as two independent clocks, coordinated so a signer never receives both at the same moment, and a reminder cadence that would outlive the expiry window is rejected with `400`.
### Get status
-Check the document-level status. When an expiration schedule is set, the response also carries `getExpiresAt()` — the signing-window deadline (ISO 8601), or `null` when expiration is off. Once that deadline passes, the document moves to the terminal `expired` status and its signing links stop working. `getRecipients()` exposes the same deadline on `getDocument().getExpiresAt()`. For per-signer detail, use [Get recipients](#get-recipients).
+Check the document-level status. When an expiration schedule is set, the response also carries `getExpiresAt()` (the signing-window deadline, ISO 8601, or `null` when expiration is off). Once that deadline passes, the document moves to the terminal `expired` status and its signing links stop working. `getRecipients()` exposes the same deadline on `getDocument().getExpiresAt()`. For per-signer detail, use [Get recipients](#get-recipients).
```java
DocumentStatusResponse status = client.turboSign().getStatus("document-uuid");
@@ -462,7 +462,7 @@ for (DocumentRecipientsResponse.RecipientSignatureStatus r : progress.getRecipie
:::tip Two status fields, and they differ on purpose
`status` is the raw database value and is only ever `pending`, `viewed` or `completed`.
-`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired` — that
+`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired`: that
is the one to display.
On a voided or expired document an unsigned signer still reads `pending` in `status`, so
@@ -475,14 +475,14 @@ the document is terminal.
:::
-Each recipient also carries a `delivery` block — `firstSentOn`, `lastSentOn`, `totalSent`,
+Each recipient also carries a `delivery` block: `firstSentOn`, `lastSentOn`, `totalSent`,
`reminderCount`, `lastRemindedAt`, `warningCount`, `lastWarningAt`. It counts the signature
request, resends, reminders, expiry warnings and terminal notices; CC notifications are
excluded, since a CC address is not a signer.
:::warning `reminderCount` and `lastRemindedAt` do not mean what their names suggest
-`reminderCount` counts **automatic (scheduled) reminders only** — the counter `maxReminders`
+`reminderCount` counts **automatic (scheduled) reminders only**: the counter `maxReminders`
caps. A manual "remind now" is a standalone nudge that must not consume the cap budget, so it
does **not** increment this, even though the email it sends *does* appear in `totalSent`.
@@ -491,7 +491,7 @@ signature-request send, each scheduled reminder, each manual "remind now" and ea
warning all stamp it. Only scheduled reminders bump `reminderCount`.
So a freshly-sent document returns a non-null `lastRemindedAt` equal to the invitation
-timestamp alongside `reminderCount: 0` — nobody has been reminded. To answer "have we actually
+timestamp alongside `reminderCount: 0`: nobody has been reminded. To answer "have we actually
chased this person", read `totalSent`, not `reminderCount`.
`warningCount` / `lastWarningAt` have no such caveat.
@@ -541,7 +541,7 @@ ResendEmailResponse result = client.turboSign().resendEmail(
### Send reminder
-Send a standalone reminder to whoever's turn it is to sign (`POST /turbosign/documents/:id/send-reminder`). It is independent of the automatic reminder cadence — it works even when reminders are disabled or the `maxReminders` cap is spent, does **not** consume that cap, and only emails signers at the **current** signing order. Use the single-arg overload to remind everyone eligible; pass a list to limit it to specific recipients, but do **not** pass an empty list, which the API rejects.
+Send a standalone reminder to whoever's turn it is to sign (`POST /turbosign/documents/:id/send-reminder`). It is independent of the automatic reminder cadence: it works even when reminders are disabled or the `maxReminders` cap is spent, does **not** consume that cap, and only emails signers at the **current** signing order. Use the single-arg overload to remind everyone eligible; pass a list to limit it to specific recipients, but do **not** pass an empty list, which the API rejects.
```java
// Remind everyone whose turn it is
@@ -549,7 +549,7 @@ SendReminderResponse reminder = client.turboSign().sendReminder("document-uuid")
for (SendReminderResponse.ReminderResult r : reminder.getResults()) {
// status is e.g. "sent", "skipped_wrong_order", "skipped_completed"
- System.out.println(r.getRecipientId() + " — " + r.getStatus());
+ System.out.println(r.getRecipientId() + ": " + r.getStatus());
}
// Or limit to specific recipients
@@ -560,7 +560,7 @@ client.turboSign().sendReminder("document-uuid", Arrays.asList("recipient-uuid-1
## Error Handling
-The SDK provides typed exceptions for different error scenarios:
+Every typed exception is a nested static class of `TurboDocxException` (`TurboDocxException.ValidationException`, not a separate top-level import) and extends `RuntimeException`, so the compiler never forces a catch:
### Error Types
@@ -581,7 +581,7 @@ The SDK provides typed exceptions for different error scenarios:
| ----------------- | -------- | ---------------------------- |
| `getMessage()` | `String` | Human-readable error message |
| `getStatusCode()` | `int` | HTTP status code |
-| `getCode()` | `String` | Error code (if available) |
+| `getCode()` | `String` | Machine-readable code; each of the 7 named subclasses falls back to its own default (e.g. `AuthenticationException`'s `AUTHENTICATION_ERROR`) whenever the API response carries none. The bare `TurboDocxException` thrown for an unmapped status (e.g. an unexpected 5xx) can return `null` |
### Example
@@ -660,53 +660,9 @@ The coordinate-based constructor takes positional arguments in this order: `new
| `required` | `Boolean` | No | Make field required |
| `backgroundColor` | `String` | No | Background color |
| `template` | `TemplateAnchor` | No | Template anchor configuration |
-| `metadata` | `FieldMetadata` | No | Conditional (IF/THEN) metadata — see below |
\*Required when not using template anchors
-#### Metadata Configuration (Conditional Fields)
-
-The optional `metadata` builds IF/THEN relationships between fields. Put a `fieldKey` on a
-controlling `checkbox`, then point each dependent field's `conditional.controllingFieldKey` back
-at it.
-
-| Property | Type | Required | Description |
-| ----------------------------------- | ----------------------- | -------- | ------------------------------------------------------------- |
-| `fieldKey` | `String` | No | Stable id on a **controlling checkbox** (`type: "checkbox"`). |
-| `conditional` | `FieldConditional` | No | Rule on a **dependent field** (see below). |
-| `conditional.controllingFieldKey` | `String` | Yes | The controlling checkbox's `fieldKey`. Must be non-empty. |
-| `conditional.operator` | `String` | Yes | `"is_checked"` or `"is_not_checked"`. |
-| `conditional.action` | `String` | Yes | `"show"` (hidden until met) or `"unlock"` (locked until met). |
-
-`FieldMetadata` and `FieldConditional` are top-level model classes — import them with
-`import com.turbodocx.models.*;`. `Field` is immutable and built with `Field.Builder` (there are
-no setters), so attach the metadata while building the field.
-
-```java
-import com.turbodocx.models.*;
-
-// Controlling checkbox — carries a stable fieldKey
-Field checkbox = new Field.Builder()
- .type("checkbox")
- .recipientEmail("reviewer@company.com")
- .page(1).x(100).y(400).width(20).height(20)
- .metadata(FieldMetadata.forFieldKey("request_changes"))
- .build();
-
-// Dependent text field — hidden until the checkbox is checked
-Field explain = new Field.Builder()
- .type("text")
- .recipientEmail("reviewer@company.com")
- .page(1).x(130).y(400).width(300).height(60)
- .metadata(FieldMetadata.forConditional(
- new FieldConditional("request_changes", "is_checked", "show")))
- .build();
-```
-
-A malformed rule returns `400 InvalidConditionalRule`; a well-formed rule whose
-`controllingFieldKey` matches no checkbox **fails open** (the field stays visible/editable). See
-[Conditional (IF/THEN) Fields](/docs/TurboSign/Conditional%20Fields).
-
#### Template Configuration
When using `template` instead of coordinates:
@@ -762,5 +718,5 @@ For detailed information about advanced configuration and API concepts, see:
- [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)
+- [API Reference](/docs/TurboSign/API%20Signatures)
- [Webhook Configuration](/docs/TurboSign/Webhooks)
diff --git a/docs/SDKs/javascript.md b/docs/SDKs/javascript.md
index 32e30d5..7cacc92 100644
--- a/docs/SDKs/javascript.md
+++ b/docs/SDKs/javascript.md
@@ -427,7 +427,7 @@ const result = await TurboSign.sendSignature({
:::tip Pass a file path directly
-`file` accepts `string | File | Buffer`. A `string` is treated as a local file path — the SDK reads it and uses the basename as the document filename, so `file: "./contract.pdf"` works without `readFileSync`. A raw `Blob` is not supported; use a `Buffer` (Node) or a `File` (browser).
+`file` accepts `string | File | Buffer`. A `string` is treated as a local file path: the SDK reads it and uses the basename as the document filename, so `file: "./contract.pdf"` works without `readFileSync`. A raw `Blob` is not supported; use a `Buffer` (Node) or a `File` (browser).
When `file` is a `Buffer`, the filename defaults to `document.pdf` (extension detected from the content). Pass `fileName` to control it:
@@ -783,7 +783,7 @@ const { documentId } = await TurboSign.sendSignature({
### Reminders & expiration schedule
-`sendSignature` (and `createSignatureReviewLink`) accept an optional **reminder and expiration schedule**. Both features are **off by default** — omit these fields and the send behaves exactly as before. The resolved schedule is **frozen onto the document when it is sent**, so later changes to your org defaults never touch a document already out for signature.
+`sendSignature` (and `createSignatureReviewLink`) accept an optional **reminder and expiration schedule**. Both features are **off by default**: omit these fields and the send behaves exactly as before. The resolved schedule is **frozen onto the document when it is sent**, so later changes to your org defaults never touch a document already out for signature.
@@ -792,13 +792,13 @@ const { documentId } = await TurboSign.sendSignature({
const result = await TurboSign.sendSignature({
// ...fileLink, recipients, fields, etc.
- // Reminders — nudge signers who haven't signed yet
+ // Reminders: nudge signers who haven't signed yet
remindersEnabled: true,
reminderDelay: { value: 3, unit: "days" }, // time to the FIRST reminder
reminderInterval: { value: 3, unit: "days" }, // gap between later reminders
maxReminders: 5, // cap per signer
- // Expiration — close the signing window
+ // Expiration: close the signing window
expirationEnabled: true,
expireAfter: { value: 30, unit: "days" }, // how long the document stays signable
expirationWarning: { value: 3, unit: "days" }, // how far before expiry warnings start
@@ -813,13 +813,13 @@ const result = await TurboSign.sendSignature({
const result = await TurboSign.sendSignature({
// ...fileLink, recipients, fields, etc.
- // Reminders — nudge signers who haven't signed yet
+ // Reminders: nudge signers who haven't signed yet
remindersEnabled: true,
reminderDelay: { value: 3, unit: "days" }, // time to the FIRST reminder
reminderInterval: { value: 3, unit: "days" }, // gap between later reminders
maxReminders: 5, // cap per signer
- // Expiration — close the signing window
+ // Expiration: close the signing window
expirationEnabled: true,
expireAfter: { value: 30, unit: "days" }, // how long the document stays signable
expirationWarning: { value: 3, unit: "days" }, // how far before expiry warnings start
@@ -830,20 +830,20 @@ const result = await TurboSign.sendSignature({
-Durations are `{ value, unit }` objects — `unit` is `"hours"` or `"days"`, and `value` is a whole number from **1 to a maximum of 999 days (23976 hours)**.
+Durations are `{ value, unit }` objects: `unit` is `"hours"` or `"days"`, and `value` is a whole number from **1 to a maximum of 999 days (23976 hours)**.
| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `remindersEnabled` | `boolean` | `false` | Send reminder emails at all |
| `reminderDelay` | `Duration` | 3 days | Time to the **first** reminder, measured from that signer's invitation |
| `reminderInterval` | `Duration` | 3 days | Gap between **subsequent** reminders |
-| `maxReminders` | `number` | `5` | Cap per signer, range **-1..50** — `-1` unlimited, `0` none. Never caps expiry warnings |
+| `maxReminders` | `number` | `5` | Cap per signer, range **-1..50** (`-1` unlimited, `0` none). Never caps expiry warnings |
| `expirationEnabled` | `boolean` | `false` | Expire the document at all |
| `expireAfter` | `Duration` | 120 days | How long the document stays signable, counted from **sending** |
| `expirationWarning` | `Duration` | 3 days | How far **before** expiry warnings start. `0` = never warn |
| `expirationWarningInterval` | `Duration` | 1 day | Gap between warnings once they start |
-Reminders and expiry warnings run as **two independent clocks**, so a signer keeps getting reminders even after warnings begin; the two are coordinated so a reminder and a warning never land on the same tick. The API rejects a cadence that can't fit its window — for example a reminder interval that outlives `expireAfter` — with `400 InvalidSignatureSchedule`. See the [API validation rules](/docs/TurboSign/API%20Signatures#reminders--expiration) for the full list.
+Reminders and expiry warnings run as **two independent clocks**, so a signer keeps getting reminders even after warnings begin; the two are coordinated so a reminder and a warning never land on the same tick. The API rejects a cadence that can't fit its window (for example a reminder interval that outlives `expireAfter`) with `400 InvalidSignatureSchedule`. See the [API validation rules](/docs/TurboSign/API%20Signatures#reminders--expiration) for the full list.
### Send reminder
@@ -853,7 +853,7 @@ Send a **standalone reminder** to whoever's turn it is to sign. Unlike the sched
```javascript
-// Remind everyone whose turn it is — omit the recipient ids
+// Remind everyone whose turn it is: omit the recipient ids
const { results } = await TurboSign.sendReminder("document-uuid");
results.forEach((r) => {
@@ -881,7 +881,7 @@ await TurboSign.sendReminder("document-uuid", ["recipient-uuid-1"]);
-:::warning Omit — don't send an empty array
+:::warning Omit: don't send an empty array
To remind everyone eligible, **omit** `recipientIds` entirely. Passing an empty array (`[]`) is rejected with a `400`.
:::
@@ -910,7 +910,7 @@ console.log(result.status); // 'under_review' | 'completed' | 'voided' | ...
-The response carries the document-level **`status`** (`under_review`, `completed`, `voided`, `expired`, …) and **`expiresAt`** — the ISO 8601 signing-window deadline, or `undefined`/`null` when expiration is off. Once that deadline passes the document moves to the terminal **`expired`** status and its signing links stop working. The same `document.expiresAt` is returned by `getRecipients()` alongside per-recipient detail.
+The response carries the document-level **`status`** (`under_review`, `completed`, `voided`, `expired`, …) and **`expiresAt`** (the ISO 8601 signing-window deadline, or `undefined`/`null` when expiration is off). Once that deadline passes the document moves to the terminal **`expired`** status and its signing links stop working. The same `document.expiresAt` is returned by `getRecipients()` alongside per-recipient detail.
### Get recipients
@@ -950,7 +950,7 @@ const chasing = recipients.filter(
:::tip Two status fields, and they differ on purpose
`status` is the raw database value and is only ever `pending`, `viewed` or `completed`.
-`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired` — that
+`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired`: that
is the one to display.
On a voided or expired document an unsigned signer still reads `pending` in `status`, so
@@ -963,14 +963,14 @@ the document is terminal.
:::
-Each recipient also carries a `delivery` block — `firstSentOn`, `lastSentOn`, `totalSent`,
+Each recipient also carries a `delivery` block: `firstSentOn`, `lastSentOn`, `totalSent`,
`reminderCount`, `lastRemindedAt`, `warningCount`, `lastWarningAt`. It counts the signature
request, resends, reminders, expiry warnings and terminal notices; CC notifications are
excluded, since a CC address is not a signer.
:::warning `reminderCount` and `lastRemindedAt` do not mean what their names suggest
-`reminderCount` counts **automatic (scheduled) reminders only** — the counter `maxReminders`
+`reminderCount` counts **automatic (scheduled) reminders only**: the counter `maxReminders`
caps. A manual "remind now" is a standalone nudge that must not consume the cap budget, so it
does **not** increment this, even though the email it sends *does* appear in `totalSent`.
@@ -979,7 +979,7 @@ signature-request send, each scheduled reminder, each manual "remind now" and ea
warning all stamp it. Only scheduled reminders bump `reminderCount`.
So a freshly-sent document returns a non-null `lastRemindedAt` equal to the invitation
-timestamp alongside `reminderCount: 0` — nobody has been reminded. To answer "have we actually
+timestamp alongside `reminderCount: 0`: nobody has been reminded. To answer "have we actually
chased this person", read `totalSent`, not `reminderCount`.
`warningCount` / `lastWarningAt` have no such caveat.
@@ -1099,7 +1099,7 @@ 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.
+All errors are real `Error` subclasses (`instanceof` works) that extend the base `TurboDocxError`. `code` is a plain string, not an enum member: the HTTP client passes the API's own code through when the response includes one (for example `QUOTE_NOT_FOUND`), and only falls back to the class default below when it doesn't, so you can branch on `err.code` for the precise reason instead of just the HTTP category.
### Error Classes
@@ -1231,7 +1231,7 @@ try {
### Error Properties
-All errors include these properties:
+All errors include these `readonly` properties:
| Property | Type | Description |
| ------------ | --------------------- | -------------------------------- |
@@ -1307,7 +1307,7 @@ Field configuration supporting both coordinate-based and template-based position
| `required` | `boolean` | No | Whether field is required |
| `backgroundColor` | `string` | No | Background color (hex, rgb, or named) |
| `template` | `object` | No | Template anchor configuration |
-| `metadata` | `object` | No | Conditional (IF/THEN) metadata — see below |
+| `metadata` | `object` | No | Conditional (IF/THEN) metadata, see below |
\*Required when not using template anchors
@@ -1374,7 +1374,7 @@ Request configuration for `createSignatureReviewLink` and `sendSignature` method
| Property | Type | Required | Description |
| --------------------- | ------------- | ----------- | ------------------------------ |
| `file` | `string \| File \| Buffer` | Conditional | Document as a local file path, `Buffer`, or browser `File` |
-| `fileName` | `string` | No | Original filename — used when `file` is a `Buffer` (defaults to `document.`) |
+| `fileName` | `string` | No | Original filename, used when `file` is a `Buffer` (defaults to `document.`) |
| `fileLink` | `string` | Conditional | URL to document file |
| `deliverableId` | `string` | Conditional | TurboDocx deliverable ID |
| `templateId` | `string` | Conditional | TurboDocx template ID |
@@ -1382,17 +1382,17 @@ Request configuration for `createSignatureReviewLink` and `sendSignature` method
| `fields` | `Field[]` | Yes | Signature fields configuration |
| `documentName` | `string` | No | Document name |
| `documentDescription` | `string` | No | Document description |
-| `senderName` | `string` | No | Sender name — falls back to `senderName` in the SDK config, then your API key's name |
-| `senderEmail` | `string` | Conditional | Sender email — **required on the request** unless supplied via `TurboSign.configure({ senderEmail })` or `TURBODOCX_SENDER_EMAIL` |
+| `senderName` | `string` | No | Sender name, falls back to `senderName` in the SDK config, then your API key's name |
+| `senderEmail` | `string` | Conditional | Sender email, **required on the request** unless supplied via `TurboSign.configure({ senderEmail })` or `TURBODOCX_SENDER_EMAIL` |
| `ccEmails` | `string[]` | No | Array of CC email addresses |
| `remindersEnabled` | `boolean` | No | Send reminder emails to signers who haven't signed (default `false`) |
-| `reminderDelay` | `Duration` | No | `{ value, unit }` — time to the first reminder |
-| `reminderInterval` | `Duration` | No | `{ value, unit }` — gap between later reminders |
+| `reminderDelay` | `Duration` | No | `{ value, unit }`, time to the first reminder |
+| `reminderInterval` | `Duration` | No | `{ value, unit }`, gap between later reminders |
| `maxReminders` | `number` | No | Cap per signer, range **-1..50** (`-1` unlimited, `0` none, default `5`) |
| `expirationEnabled` | `boolean` | No | Close the signing window after `expireAfter` (default `false`) |
-| `expireAfter` | `Duration` | No | `{ value, unit }` — how long the document stays signable |
-| `expirationWarning` | `Duration` | No | `{ value, unit }` — how far before expiry warnings start (`0` = never warn) |
-| `expirationWarningInterval` | `Duration` | No | `{ value, unit }` — gap between warnings once they start |
+| `expireAfter` | `Duration` | No | `{ value, unit }`, how long the document stays signable |
+| `expirationWarning` | `Duration` | No | `{ value, unit }`, how far before expiry warnings start (`0` = never warn) |
+| `expirationWarningInterval` | `Duration` | No | `{ value, unit }`, gap between warnings once they start |
:::info Durations
A `Duration` is `{ value: number, unit: "hours" | "days" }`. `value` is a whole number from **1 to 999 days (23976 hours)**.
@@ -1402,11 +1402,14 @@ A `Duration` is `{ value: number, unit: "hours" | "days" }`. `value` is a whole
Exactly one file source is required: `file`, `fileLink`, `deliverableId`, or `templateId`.
:::
-:::caution Sender identity is always required for TurboSign
+:::caution Sender email is enforced by the SDK, not by a `SenderEmailRequired`/`SenderNameRequired` API error
Unlike TurboQuote (where the sender comes from the org quote template and there is no per-request
-field), TurboSign resolves the sender **from the request body**. If no sender email can be resolved
-from the request, the SDK config, or the environment, the API returns `400 SenderEmailRequired`;
-if no sender name can be resolved it returns `400 SenderNameRequired`.
+field), TurboSign expects the sender to come from the request body, `TurboSign.configure({ senderEmail })`,
+or the `TURBODOCX_SENDER_EMAIL` environment variable. The **SDK enforces this itself**: `TurboSign.configure()`
+throws a `ValidationError` if no `senderEmail` is configured (client-side, before any request is sent).
+The API itself does not reject a send that omits a sender: if no sender email or name can be resolved,
+it falls back to a generic TurboDocx sender identity rather than returning `400 SenderEmailRequired` or
+`400 SenderNameRequired`.
:::
---
diff --git a/docs/SDKs/partner-go.md b/docs/SDKs/partner-go.md
index f776753..2440a5d 100644
--- a/docs/SDKs/partner-go.md
+++ b/docs/SDKs/partner-go.md
@@ -27,12 +27,12 @@ import QuickstartSkillNudge from '@site/src/components/QuickstartSkillNudge';
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.
+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.
+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
@@ -80,7 +80,7 @@ func main() {
Name: "Production Key",
Role: "admin",
})
- fmt.Printf("API Key: %s\n", key.Data.Key) // Save this — only shown once!
+ fmt.Printf("API Key: %s\n", key.Data.Key) // Save this, only shown once!
}
```
@@ -98,7 +98,7 @@ go get github.com/TurboDocx/SDK/packages/go-sdk
- 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.
+The Go SDK uses only the standard library: no third-party packages required. This makes it easy to integrate into any Go project.
:::
---
@@ -440,7 +440,7 @@ 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.
+The full API key is only returned once during creation. Store it securely: you won't be able to retrieve it again.
:::
### `ListOrganizationAPIKeys()`
@@ -551,11 +551,11 @@ result, err := partner.RevokePartnerAPIKey(ctx, "partner-key-uuid-here")
## Partner User Management
:::danger Partner users use a different role enum
-Partner portal users take `admin`, `member`, or `viewer`. **Organization** users and organization API keys take `admin`, `contributor`, `user`, or `viewer`. The two enums do not overlap beyond `admin`/`viewer` — `"member"` is rejected on an org call, and `"contributor"`/`"user"` are rejected on a partner call. See [Role Enums](#organization-user-roles).
+Partner portal users take `admin`, `member`, or `viewer`. **Organization** users and organization API keys take `admin`, `contributor`, `user`, or `viewer`. The two enums do not overlap beyond `admin`/`viewer`: `"member"` is rejected on an org call, and `"contributor"`/`"user"` are rejected on a partner call. See [Role Enums](#organization-user-roles).
:::
:::caution `Permissions` is all-or-nothing
-The `Permissions` object itself is optional, but if you send it, **all seven fields are required**. There is no partial permissions update — the API rejects an incomplete object with `*ValidationError` (400). Because `PartnerPermissions` is a struct of plain `bool`s, any field you leave out silently serializes as `false` rather than "unchanged": read the current values first and re-send them with your change applied.
+The `Permissions` object itself is optional, but if you send it, **all seven fields are required**. There is no partial permissions update: the API rejects an incomplete object with `*ValidationError` (400). Because `PartnerPermissions` is a struct of plain `bool`s, any field you leave out silently serializes as `false` rather than "unchanged": read the current values first and re-send them with your change applied.
:::
### `AddUserToPartnerPortal()`
@@ -599,7 +599,7 @@ for _, user := range result.Data.Results {
### `UpdatePartnerUserPermissions()`
-Update a partner user's role and permissions. If you set `Permissions`, populate **all seven fields** — a partial object is a 400, and unset bools default to `false`.
+Update a partner user's role and permissions. If you set `Permissions`, populate **all seven fields**: a partial object is a 400, and unset bools default to `false`.
```go
result, err := partner.UpdatePartnerUserPermissions(ctx, "partner-user-uuid-here",
@@ -707,14 +707,14 @@ These are limits and capabilities you can configure for each organization:
:::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
+- `turbodocx.IntPtr(25)`, for `*int` fields
+- `turbodocx.Int64Ptr(5368709120)`, for `*int64` fields (storage)
+- `turbodocx.BoolPtr(true)`, for `*bool` fields
:::
### Tracking (Usage Counters)
-Current consumption against the limits above. TurboDocx maintains these automatically, but `UpdateOrganizationEntitlements()` **accepts a `Tracking` object** — useful for seeding counters when migrating an existing customer:
+Current consumption against the limits above. TurboDocx maintains these automatically, but `UpdateOrganizationEntitlements()` **accepts a `Tracking` object**, useful for seeding counters when migrating an existing customer:
| Field | Type | Description |
|-------|------|-------------|
@@ -737,7 +737,7 @@ Every counter except `CurrentAICredits` floors at `0`. Only `CurrentAICredits` a
## Preferences Reference
-TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly — the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
+TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly: the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
@@ -841,7 +841,7 @@ permissions := turbodocx.PartnerPermissions{
## Error Handling
-The SDK provides typed errors for different error scenarios:
+`partner.CreateOrganization` and the other partner calls return `AuthorizationError` when the partner API key lacks the scope for the route, since partner keys are scoped separately from organization keys:
```go
import "errors"
@@ -852,6 +852,7 @@ if err != nil {
var authzErr *turbodocx.AuthorizationError
var validErr *turbodocx.ValidationError
var notFoundErr *turbodocx.NotFoundError
+ var conflictErr *turbodocx.ConflictError
var rateLimitErr *turbodocx.RateLimitError
var networkErr *turbodocx.NetworkError
@@ -868,6 +869,9 @@ if err != nil {
case errors.As(err, ¬FoundErr):
// 404 - Organization or resource not found
fmt.Printf("Not found: %s\n", notFoundErr.Message)
+ case errors.As(err, &conflictErr):
+ // 409 - Resource conflict (e.g. AddUserToPartnerPortal on an existing user)
+ fmt.Printf("Conflict: %s\n", conflictErr.Message)
case errors.As(err, &rateLimitErr):
// 429 - Rate limit exceeded
fmt.Printf("Rate limit: %s\n", rateLimitErr.Message)
@@ -880,17 +884,7 @@ if err != nil {
}
```
-### Error Types
-
-| Error Type | Status Code | Description |
-|------------|-------------|-------------|
-| `TurboDocxError` | varies | Base error for all SDK errors |
-| `AuthenticationError` | 401 | Invalid or missing API credentials |
-| `AuthorizationError` | 403 | Authenticated but the key lacks the required scope |
-| `ValidationError` | 400 | Invalid request parameters |
-| `NotFoundError` | 404 | Resource not found |
-| `RateLimitError` | 429 | Too many requests |
-| `NetworkError` | - | Network connectivity issues |
+The full typed-error table and HTTP status mapping is documented once in the [Go SDK's Error Handling reference](./go.md#error-handling); partner calls use the same `AuthenticationError`/`AuthorizationError`/`ValidationError`/`NotFoundError`/`ConflictError`/`RateLimitError`/`NetworkError` types (for example, `AddUserToPartnerPortal()` returns a `*ConflictError` (409) when the target user already has partner-portal access).
---
@@ -977,4 +971,4 @@ func main() {
- [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
+- [TurboSign Go SDK](/docs/SDKs/go): for digital signature operations
diff --git a/docs/SDKs/partner-java.md b/docs/SDKs/partner-java.md
index 9e806f3..3531996 100644
--- a/docs/SDKs/partner-java.md
+++ b/docs/SDKs/partner-java.md
@@ -32,7 +32,7 @@ The official TurboDocx Partner SDK for Java applications. Build multi-tenant Saa
:::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.
+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
@@ -66,7 +66,7 @@ public class Main {
// 4. Create an API key
JsonObject key = client.turboPartner().createOrganizationApiKey(orgId, "Production Key", "admin");
- System.out.println("API Key: " + key.getAsJsonObject("data").get("key").getAsString()); // Save this — only shown once!
+ System.out.println("API Key: " + key.getAsJsonObject("data").get("key").getAsString()); // Save this, only shown once!
}
}
```
@@ -82,7 +82,7 @@ public class Main {
com.turbodocx
turbodocx-sdk
- 0.5.0
+ 0.7.0
```
@@ -90,14 +90,14 @@ public class Main {
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.5.0")
+implementation("com.turbodocx:turbodocx-sdk:0.7.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.5.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
```
@@ -156,7 +156,7 @@ export TURBODOCX_PARTNER_ID=your-partner-uuid
```
:::info Responses are raw `JsonObject`
-Every `TurboPartner` method returns a Gson `JsonObject` containing the raw API response — `success`, `data`, and sometimes `message`. Unlike the TurboSign and Deliverable modules, partner responses are **not** unwrapped into typed models, so read them with `getAsJsonObject("data")`, `getAsJsonArray("results")`, `getAsString()`, and friends. Iterating a results array needs `com.google.gson.JsonElement` alongside `com.google.gson.JsonObject`. Every method throws `IOException` on transport failure.
+Every `TurboPartner` method returns a Gson `JsonObject` containing the raw API response: `success`, `data`, and sometimes `message`. Unlike the TurboSign and Deliverable modules, partner responses are **not** unwrapped into typed models, so read them with `getAsJsonObject("data")`, `getAsJsonArray("results")`, `getAsString()`, and friends. Iterating a results array needs `com.google.gson.JsonElement` alongside `com.google.gson.JsonObject`. Every method throws `IOException` on transport failure.
:::
---
@@ -269,7 +269,7 @@ JsonObject result = client.turboPartner().updateOrganizationInfo(
### `updateOrganizationEntitlements()`
-Update an organization's feature limits and capabilities. Both `features` and `tracking` are optional — pass `null` for the one you are not changing.
+Update an organization's feature limits and capabilities. Both `features` and `tracking` are optional: pass `null` for the one you are not changing.
```java
Map features = new LinkedHashMap<>();
@@ -442,7 +442,7 @@ System.out.println("Full Key: " + data.get("key").getAsString()); // Only shown
```
:::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.
+The full API key is only returned once during creation. Store it securely: you won't be able to retrieve it again.
:::
### `listOrganizationApiKeys()`
@@ -534,7 +534,7 @@ for (JsonElement element : result.getAsJsonObject("data").getAsJsonArray("result
### `updatePartnerApiKey()`
-Update a partner API key. The argument order is `keyId, name, description, scopes` — pass `null` for anything you want to leave unchanged.
+Update a partner API key. The argument order is `keyId, name, description, scopes`: pass `null` for anything you want to leave unchanged.
```java
JsonObject result = client.turboPartner().updatePartnerApiKey(
@@ -558,11 +558,11 @@ JsonObject result = client.turboPartner().revokePartnerApiKey("partner-key-uuid-
## Partner User Management
:::danger Partner users use different role values
-Partner portal users take `"admin"`, `"member"`, or `"viewer"`. **Organization** users and organization API keys take `"admin"`, `"contributor"`, `"user"`, or `"viewer"`. The two sets do not overlap beyond `admin`/`viewer` — `"member"` is rejected on an org call, and `"contributor"`/`"user"` are rejected on a partner call. See [Role Values](#role-values).
+Partner portal users take `"admin"`, `"member"`, or `"viewer"`. **Organization** users and organization API keys take `"admin"`, `"contributor"`, `"user"`, or `"viewer"`. The two sets do not overlap beyond `admin`/`viewer`: `"member"` is rejected on an org call, and `"contributor"`/`"user"` are rejected on a partner call. See [Role Values](#role-values).
:::
:::caution `permissions` is all-or-nothing
-`addUserToPartnerPortal()` **requires** a permissions map containing all seven keys. On `updatePartnerUserPermissions()` the map is optional (`null` keeps the current values), but if you send it, **all seven keys are required**. There is no partial permissions update — the API rejects an incomplete map with `TurboDocxException.ValidationException` (400). Read the current values first and re-send them with your change applied.
+`addUserToPartnerPortal()` **requires** a permissions map containing all seven keys. On `updatePartnerUserPermissions()` the map is optional (`null` keeps the current values), but if you send it, **all seven keys are required**. There is no partial permissions update: the API rejects an incomplete map with `TurboDocxException.ValidationException` (400). Read the current values first and re-send them with your change applied.
:::
### `addUserToPartnerPortal()`
@@ -573,7 +573,7 @@ Add a user to the partner portal with specific permissions.
import java.util.LinkedHashMap;
import java.util.Map;
-// Required on add — all 7 keys must be present.
+// Required on add: all 7 keys must be present.
Map permissions = new LinkedHashMap<>();
permissions.put("canManageOrgs", true); // Create, update, delete organizations
permissions.put("canManageOrgUsers", true); // Manage users within organizations
@@ -607,7 +607,7 @@ for (JsonElement element : result.getAsJsonObject("data").getAsJsonArray("result
### `updatePartnerUserPermissions()`
-Update a partner user's role and/or permissions. Pass `null` for `role` or `permissions` to keep the current value — but if you pass `permissions`, supply **all seven keys**; a partial map is a 400.
+Update a partner user's role and/or permissions. Pass `null` for `role` or `permissions` to keep the current value, but if you pass `permissions`, supply **all seven keys**; a partial map is a 400.
```java
Map permissions = new LinkedHashMap<>();
@@ -648,7 +648,7 @@ JsonObject result = client.turboPartner().removeUserFromPartnerPortal("partner-u
### `getPartnerAuditLogs()`
-Get audit logs for all partner activities with filtering. All nine arguments are positional — pass `null` for any filter you don't want.
+Get audit logs for all partner activities with filtering. All nine arguments are positional: pass `null` for any filter you don't want.
```java
JsonObject result = client.turboPartner().getPartnerAuditLogs(
@@ -722,12 +722,12 @@ These are limits and capabilities you can configure for each organization:
| `enableBulkSending` | boolean | Enable bulk document sending |
:::info Map keys stay camelCase
-The `features`, `tracking`, and `permissions` maps are serialized straight into the JSON request body, so the keys must match exactly as written above (`maxUsers`, `hasTDAI`, `canManageOrgAPIKeys`) — Java naming conventions do not apply to request-body keys.
+The `features`, `tracking`, and `permissions` maps are serialized straight into the JSON request body, so the keys must match exactly as written above (`maxUsers`, `hasTDAI`, `canManageOrgAPIKeys`): Java naming conventions do not apply to request-body keys.
:::
### Tracking (Usage Counters)
-Current consumption against the limits above. TurboDocx maintains these automatically, but `updateOrganizationEntitlements()` **accepts a `tracking` map** — useful for seeding counters when migrating an existing customer:
+Current consumption against the limits above. TurboDocx maintains these automatically, but `updateOrganizationEntitlements()` **accepts a `tracking` map**, useful for seeding counters when migrating an existing customer:
| Field | Type | Description |
|-------|------|-------------|
@@ -746,7 +746,7 @@ Every counter except `currentAICredits` floors at `0`. Only `currentAICredits` a
## Preferences Reference
-TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly — the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
+TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly: the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
@@ -763,7 +763,7 @@ TurboSign display preferences you can read and set per organization. Every key i
### PartnerScope (22 Scopes)
-`com.turbodocx.PartnerScope` is a constants class of `String` values — there is no scope enum. Pass them as a `List` to `createPartnerApiKey()` and `updatePartnerApiKey()`.
+`com.turbodocx.PartnerScope` is a constants class of `String` values: there is no scope enum. Pass them as a `List` to `createPartnerApiKey()` and `updatePartnerApiKey()`.
```java
import com.turbodocx.PartnerScope;
@@ -807,9 +807,9 @@ PartnerScope.AUDIT_READ // "audit:read"
### Role Values
-Roles are plain `String` values in the Java SDK — there is no role enum.
+Roles are plain `String` values in the Java SDK: there is no role enum.
-**Organization users and organization API keys** — used by `addUserToOrganization()`, `updateOrganizationUserRole()`, `createOrganizationApiKey()`, and `updateOrganizationApiKey()`:
+**Organization users and organization API keys**, used by `addUserToOrganization()`, `updateOrganizationUserRole()`, `createOrganizationApiKey()`, and `updateOrganizationApiKey()`:
| Value | Description |
| --------------- | ---------------------------- |
@@ -818,7 +818,7 @@ Roles are plain `String` values in the Java SDK — there is no role enum.
| `"user"` | Standard user access |
| `"viewer"` | Read-only access |
-**Partner portal users** — used by `addUserToPartnerPortal()` and `updatePartnerUserPermissions()` only:
+**Partner portal users**, used by `addUserToPartnerPortal()` and `updatePartnerUserPermissions()` only:
| Value | Description |
| ---------- | -------------------------------------------- |
@@ -848,7 +848,7 @@ All seven keys are required whenever a permissions map is sent. Partial maps are
## Error Handling
-The SDK provides typed exceptions for different error scenarios. They all extend `TurboDocxException`, which is a `RuntimeException`, so catch it after any checked `IOException` handling:
+Partner calls throw `TurboDocxException.AuthenticationException` when the partner API key or partner ID is wrong, since partner credentials are validated separately from organization API keys. Every typed exception extends `TurboDocxException`, a `RuntimeException`, so catch it after any checked `IOException` handling:
```java
import com.turbodocx.TurboDocxException;
@@ -881,31 +881,14 @@ try {
}
```
-### Error Types
-
-| Error Type | Status Code | Description |
-| -------------------------------------------- | ----------- | -------------------------------------------------- |
-| `TurboDocxException` | varies | Base exception for all API errors |
-| `TurboDocxException.AuthenticationException` | 401 | Invalid or missing partner credentials |
-| `TurboDocxException.ValidationException` | 400 | Invalid request parameters |
-| `TurboDocxException.AuthorizationException` | 403 | Partner API key lacks the required scope |
-| `TurboDocxException.NotFoundException` | 404 | Resource not found |
-| `TurboDocxException.RateLimitException` | 429 | Too many requests |
+The full typed-exception table and HTTP status mapping is documented once in the [Java SDK's Error Handling reference](./java.md#error-handling); partner calls use the same `AuthenticationException`/`ValidationException`/`AuthorizationException`/`NotFoundException`/`RateLimitException` types.
Transport failures are **not** wrapped: the partner client propagates OkHttp's checked `IOException` directly, so catch `IOException` for connectivity problems rather than `TurboDocxException.NetworkException`.
-:::caution 409 conflicts arrive as the base exception
-`TurboDocxException.ConflictException` exists in the SDK, but the partner client does **not** raise it — a 409 (for example, a user that already exists) surfaces as the base `TurboDocxException` with `getStatusCode() == 409`. Handle it in the base `catch` block rather than adding a `ConflictException` catch, which would never fire on a partner call.
+:::tip 409 Conflicts
+`TurboDocxException.ConflictException` is raised for 409 responses on partner calls too (for example, a user that already exists), the same way as `AuthenticationException`, `ValidationException`, `AuthorizationException`, `NotFoundException`, and `RateLimitException`. Add a `catch (TurboDocxException.ConflictException e)` block if you want to handle conflicts separately from the base `TurboDocxException` catch-all.
:::
-### Error Properties
-
-| Property | Type | Description |
-| ----------------- | -------- | ---------------------------- |
-| `getMessage()` | `String` | Human-readable error message |
-| `getStatusCode()` | `int` | HTTP status code |
-| `getCode()` | `String` | Error code (if available) |
-
---
## Complete Example
@@ -974,4 +957,4 @@ public class PartnerOnboarding {
- [GitHub Repository](https://github.com/TurboDocx/SDK/tree/main/packages/java-sdk)
- [Maven Central](https://search.maven.org/artifact/com.turbodocx/turbodocx-sdk)
-- [TurboSign Java SDK](/docs/SDKs/java) — For digital signature operations
+- [TurboSign Java SDK](/docs/SDKs/java): for digital signature operations
diff --git a/docs/SDKs/partner-javascript.md b/docs/SDKs/partner-javascript.md
index 67d20d4..3ed325f 100644
--- a/docs/SDKs/partner-javascript.md
+++ b/docs/SDKs/partner-javascript.md
@@ -33,7 +33,7 @@ The official TurboDocx Partner SDK for JavaScript and TypeScript applications. B
:::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.
+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
@@ -69,7 +69,7 @@ const key = await TurboPartner.createOrganizationApiKey(orgId, {
name: 'Production Key',
role: 'admin',
});
-console.log(`API Key: ${key.data.key}`); // Save this — only shown once!
+console.log(`API Key: ${key.data.key}`); // Save this, only shown once!
```
---
@@ -90,7 +90,7 @@ pnpm add @turbodocx/sdk
- 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.
+This SDK includes complete TypeScript type definitions for all request/response types, enums, and configuration options: no additional `@types` packages needed.
:::
---
@@ -404,7 +404,7 @@ const result = await TurboPartner.createOrganizationApiKey(
'org-uuid-here',
{
name: 'Production API Key',
- role: 'admin', // 'admin' | 'contributor' | 'user' | 'viewer' — the ORG role enum
+ role: 'admin', // 'admin' | 'contributor' | 'user' | 'viewer' (the ORG role enum)
}
);
@@ -413,7 +413,7 @@ 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.
+The full API key is only returned once during creation. Store it securely: you won't be able to retrieve it again.
:::
### `listOrganizationApiKeys()`
@@ -522,11 +522,11 @@ const result = await TurboPartner.revokePartnerApiKey('partner-key-uuid-here');
## Partner User Management
:::danger Partner users use a different role enum
-Partner portal users take `'admin' | 'member' | 'viewer'`. **Organization** users and organization API keys take `'admin' | 'contributor' | 'user' | 'viewer'`. The two enums do not overlap beyond `admin`/`viewer` — `'member'` is rejected on an org call, and `'contributor'`/`'user'` are rejected on a partner call. See [Role Enums](#orguserrole-organization-users).
+Partner portal users take `'admin' | 'member' | 'viewer'`. **Organization** users and organization API keys take `'admin' | 'contributor' | 'user' | 'viewer'`. The two enums do not overlap beyond `admin`/`viewer`: `'member'` is rejected on an org call, and `'contributor'`/`'user'` are rejected on a partner call. See [Role Enums](#orguserrole-organization-users-and-org-api-keys).
:::
:::caution `permissions` is all-or-nothing
-On `addUserToPartnerPortal()` the `permissions` object is **required**. On `updatePartnerUserPermissions()` it is optional, but if you send it, **all seven keys are required**. Either way there is no partial permissions update — omitting even one key is a `ValidationError` (400). Always send the complete object; read the current values first and re-send them with your change applied.
+On `addUserToPartnerPortal()` the `permissions` object is **required**. On `updatePartnerUserPermissions()` it is optional, but if you send it, **all seven keys are required**. Either way there is no partial permissions update: omitting even one key is a `ValidationError` (400). Always send the complete object; read the current values first and re-send them with your change applied.
:::
### `addUserToPartnerPortal()`
@@ -536,7 +536,7 @@ 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' — the PARTNER role enum
+ role: 'admin', // 'admin' | 'member' | 'viewer' (the PARTNER role enum)
// Required on this method, and all 7 keys must be present.
permissions: {
canManageOrgs: true,
@@ -566,7 +566,7 @@ for (const user of result.data.results) {
### `updatePartnerUserPermissions()`
-Update a partner user's role and permissions. If you include `permissions`, send **all seven keys** — a partial object is a 400.
+Update a partner user's role and permissions. If you include `permissions`, send **all seven keys**: a partial object is a 400.
```typescript
const result = await TurboPartner.updatePartnerUserPermissions(
@@ -674,7 +674,7 @@ These are limits and capabilities you can configure for each organization:
### Tracking (Usage Counters)
-Current consumption against the limits above. TurboDocx maintains these automatically, but `updateOrganizationEntitlements()` **accepts a `tracking` object** — useful for seeding counters when migrating an existing customer:
+Current consumption against the limits above. TurboDocx maintains these automatically, but `updateOrganizationEntitlements()` **accepts a `tracking` object**, useful for seeding counters when migrating an existing customer:
| Field | Type | Description |
|-------|------|-------------|
@@ -693,7 +693,7 @@ Every counter except `currentAICredits` floors at `0`. Only `currentAICredits` a
## Preferences Reference
-TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly — the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
+TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly: the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
@@ -801,7 +801,7 @@ interface PartnerPermissions {
## Error Handling
-The SDK provides typed error classes for different error scenarios:
+`TurboPartner.createOrganization()` and the other partner calls reject with `AuthorizationError` when the partner API key lacks the scope for the route, since partner keys are scoped separately from organization keys:
```typescript
import {
@@ -846,18 +846,7 @@ try {
}
```
-### Error Classes
-
-| Error Class | Status Code | Description |
-|-------------|-------------|-------------|
-| `TurboDocxError` | varies | Base error for all SDK errors |
-| `AuthenticationError` | 401 | Invalid or missing API credentials |
-| `AuthorizationError` | 403 | API key lacks required permissions (scope) |
-| `ValidationError` | 400 | Invalid request parameters |
-| `NotFoundError` | 404 | Resource not found |
-| `ConflictError` | 409 | Resource conflict |
-| `RateLimitError` | 429 | Too many requests |
-| `NetworkError` | - | Network connectivity issues |
+The full typed-error table and HTTP status mapping is documented once in the [JavaScript / TypeScript SDK's Error Handling reference](./javascript.md#error-handling); partner calls use the same `AuthenticationError`/`AuthorizationError`/`ValidationError`/`NotFoundError`/`ConflictError`/`RateLimitError`/`NetworkError` types.
---
@@ -931,4 +920,4 @@ try {
- [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
+- [TurboSign JavaScript SDK](/docs/SDKs/javascript): for digital signature operations
diff --git a/docs/SDKs/partner-php.md b/docs/SDKs/partner-php.md
index 1c944c6..1c84249 100644
--- a/docs/SDKs/partner-php.md
+++ b/docs/SDKs/partner-php.md
@@ -32,7 +32,7 @@ The official TurboDocx Partner SDK for PHP applications. Build multi-tenant SaaS
:::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.
+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
@@ -77,7 +77,7 @@ $user = TurboPartner::addUserToOrganization($orgId,
$key = TurboPartner::createOrganizationApiKey($orgId,
new CreateOrgApiKeyRequest(name: 'Production Key', role: 'admin')
);
-echo "API Key: {$key->data->key}\n"; // Save this — only shown once!
+echo "API Key: {$key->data->key}\n"; // Save this, only shown once!
```
---
@@ -449,7 +449,7 @@ 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.
+The full API key is only returned once during creation. Store it securely: you won't be able to retrieve it again.
:::
### `listOrganizationApiKeys()`
@@ -573,11 +573,11 @@ $result = TurboPartner::revokePartnerApiKey('partner-key-uuid-here');
## Partner User Management
:::danger Partner users use a different role enum
-Partner portal users take `admin`, `member`, or `viewer` (`PartnerUserRole`). **Organization** users and organization API keys take `admin`, `contributor`, `user`, or `viewer` (`OrgUserRole`). The two enums do not overlap beyond `admin`/`viewer` — `'member'` is rejected on an org call, and `'contributor'`/`'user'` are rejected on a partner call. See [Role Enums](#orguserrole-organization-users).
+Partner portal users take `admin`, `member`, or `viewer` (`PartnerUserRole`). **Organization** users and organization API keys take `admin`, `contributor`, `user`, or `viewer` (`OrgUserRole`). The two enums do not overlap beyond `admin`/`viewer`: `'member'` is rejected on an org call, and `'contributor'`/`'user'` are rejected on a partner call. See [Role Enums](#orguserrole-organization-users-and-org-api-keys).
:::
:::caution `permissions` is all-or-nothing
-`AddPartnerUserRequest` **requires** `permissions` — omitting it is an `ArgumentCountError`. On `UpdatePartnerUserRequest` it is optional, but if you send it, **all seven arguments are required**. There is no partial permissions update — the API rejects an incomplete object with `ValidationException` (400). Read the current values first and re-send them with your change applied.
+`AddPartnerUserRequest` **requires** `permissions`: omitting it is an `ArgumentCountError`. On `UpdatePartnerUserRequest` it is optional, but if you send it, **all seven arguments are required**. There is no partial permissions update: the API rejects an incomplete object with `ValidationException` (400). Read the current values first and re-send them with your change applied.
:::
### `addUserToPartnerPortal()`
@@ -592,7 +592,7 @@ $result = TurboPartner::addUserToPartnerPortal(
new AddPartnerUserRequest(
email: 'admin@partner.com',
role: 'admin', // PARTNER role enum: admin, member, or viewer
- // Required on add — all 7 arguments must be supplied.
+ // Required on add: all 7 arguments must be supplied.
permissions: new PartnerPermissions(
canManageOrgs: true,
canManageOrgUsers: true,
@@ -626,7 +626,7 @@ foreach ($result->results as $user) {
### `updatePartnerUserPermissions()`
-Update a partner user's role and permissions. If you pass `permissions`, supply **all seven arguments** — a partial object is a 400.
+Update a partner user's role and permissions. If you pass `permissions`, supply **all seven arguments**: a partial object is a 400.
```php
use TurboDocx\Types\Requests\Partner\UpdatePartnerUserRequest;
@@ -736,7 +736,7 @@ These are limits and capabilities you can configure for each organization:
### Tracking (Usage Counters)
-Current consumption against the limits above. TurboDocx maintains these automatically, but `updateOrganizationEntitlements()` **accepts a `tracking` array** — useful for seeding counters when migrating an existing customer:
+Current consumption against the limits above. TurboDocx maintains these automatically, but `updateOrganizationEntitlements()` **accepts a `tracking` array**, useful for seeding counters when migrating an existing customer:
| Field | Type | Description |
|-------|------|-------------|
@@ -755,7 +755,7 @@ Every counter except `currentAICredits` floors at `0`. Only `currentAICredits` a
## Preferences Reference
-TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly — the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
+TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly: the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
@@ -863,7 +863,7 @@ $permissions = new PartnerPermissions(
## Error Handling
-The SDK provides typed exceptions for different error scenarios:
+`TurboPartner::createOrganization()` and the other partner calls throw `AuthenticationException` when the partner API key is invalid, missing, or the partner account is inactive, and `NotFoundException` when the `partnerId` doesn't match the key's own partner, since partner credentials are validated separately from organization API keys:
```php
use TurboDocx\Exceptions\AuthenticationException;
@@ -875,13 +875,13 @@ use TurboDocx\Exceptions\NetworkException;
try {
$result = TurboPartner::createOrganization(/* ... */);
} catch (AuthenticationException $e) {
- // 401 - Invalid API key or partner ID
+ // 401 - Invalid or missing partner API key
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
+ // 404 - Organization/resource not found, or partnerId doesn't match the key
echo "Not found: {$e->getMessage()}\n";
} catch (RateLimitException $e) {
// 429 - Rate limit exceeded
@@ -892,16 +892,7 @@ try {
}
```
-### 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 |
+The full typed-exception table and HTTP status mapping is documented once in the [PHP SDK's Error Handling reference](./php.md#error-handling).
---
@@ -983,4 +974,4 @@ try {
- [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
+- [TurboSign PHP SDK](/docs/SDKs/php): for digital signature operations
diff --git a/docs/SDKs/partner-python.md b/docs/SDKs/partner-python.md
index 0765004..6f5b51d 100644
--- a/docs/SDKs/partner-python.md
+++ b/docs/SDKs/partner-python.md
@@ -32,7 +32,7 @@ The official TurboDocx Partner SDK for Python applications. Build multi-tenant S
:::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.
+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
@@ -70,7 +70,7 @@ async def main():
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!
+ print(f"API Key: {key['data']['key']}") # Save this, only shown once!
asyncio.run(main())
```
@@ -392,7 +392,7 @@ 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.
+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()`
@@ -510,13 +510,13 @@ result = await TurboPartner.revoke_partner_api_key("partner-key-uuid-here")
## Partner User Management
:::danger Partner users use a different role enum
-Partner portal users take `admin`, `member`, or `viewer`. **Organization** users and organization API keys take `admin`, `contributor`, `user`, or `viewer`. The two enums do not overlap beyond `admin`/`viewer` — `"member"` is rejected on an org call, and `"contributor"`/`"user"` are rejected on a partner call. See [Role Enums](#organization-user-roles).
+Partner portal users take `admin`, `member`, or `viewer`. **Organization** users and organization API keys take `admin`, `contributor`, `user`, or `viewer`. The two enums do not overlap beyond `admin`/`viewer`: `"member"` is rejected on an org call, and `"contributor"`/`"user"` are rejected on a partner call. See [Role Enums](#organization-user-roles).
:::
:::caution `permissions` is all-or-nothing
-On `add_user_to_partner_portal()`, `permissions` is a **required** keyword argument — omitting it raises a Python `TypeError` before any request is sent. On `update_partner_user_permissions()`, the `permissions` dict itself is optional.
+On `add_user_to_partner_portal()`, `permissions` is a **required** keyword argument: omitting it raises a Python `TypeError` before any request is sent. On `update_partner_user_permissions()`, the `permissions` dict itself is optional.
-Either way, if you send `permissions`, **all seven keys are required**. There is no partial permissions update — omitting even one key raises `ValidationError` (400). Always send the complete dict; read the current values first and re-send them with your change applied.
+Either way, if you send `permissions`, **all seven keys are required**. There is no partial permissions update: omitting even one key raises `ValidationError` (400). Always send the complete dict; read the current values first and re-send them with your change applied.
:::
### `add_user_to_partner_portal()`
@@ -559,7 +559,7 @@ for user in result["data"]["results"]:
### `update_partner_user_permissions()`
-Update a partner user's role and permissions. If you pass `permissions`, send **all seven keys** — a partial dict is a 400.
+Update a partner user's role and permissions. If you pass `permissions`, send **all seven keys**: a partial dict is a 400.
```python
result = await TurboPartner.update_partner_user_permissions(
@@ -670,7 +670,7 @@ features={"maxUsers": 25, "hasTDAI": True}
### Tracking (Usage Counters)
-Current consumption against the limits above. TurboDocx maintains these automatically, but `update_organization_entitlements()` **accepts a `tracking` dict** — useful for seeding counters when migrating an existing customer:
+Current consumption against the limits above. TurboDocx maintains these automatically, but `update_organization_entitlements()` **accepts a `tracking` dict**, useful for seeding counters when migrating an existing customer:
| Field | Type | Description |
|-------|------|-------------|
@@ -688,7 +688,7 @@ Every counter except `currentAICredits` floors at `0`. Only `currentAICredits` a
## Preferences Reference
-TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly — the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
+TurboSign display preferences you can read and set per organization. Every key is a boolean and is validated strictly: the strings `"true"` / `"false"` are rejected with a 400, so pass real booleans. The API returns only these keys and never any of the organization's other settings.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
@@ -790,7 +790,7 @@ permissions = {
## Error Handling
-The SDK provides typed exceptions for different error scenarios:
+`TurboPartner.create_organization()` and the other partner calls raise `AuthorizationError` when the partner API key lacks the scope for the route, since partner keys are scoped separately from organization keys:
```python
from turbodocx_sdk import (
@@ -834,18 +834,7 @@ except TurboDocxError as e:
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 |
-| `AuthorizationError` | 403 | Valid credentials without permission for this operation |
-| `ValidationError` | 400 | Invalid request parameters |
-| `NotFoundError` | 404 | Resource not found |
-| `ConflictError` | 409 | Request conflicts with current resource state |
-| `RateLimitError` | 429 | Too many requests |
-| `NetworkError` | - | Network connectivity issues |
+The full typed-error table and HTTP status mapping is documented once in the [Python SDK's Error Handling reference](./python.md#error-handling); partner calls use the same `AuthenticationError`/`AuthorizationError`/`ValidationError`/`NotFoundError`/`ConflictError`/`RateLimitError`/`NetworkError` types.
---
@@ -905,5 +894,5 @@ asyncio.run(main())
- [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
+- [TurboSign Python SDK](/docs/SDKs/python): for digital signature operations
+- [SDKs Overview](/docs/SDKs/): all TurboDocx SDKs
diff --git a/docs/SDKs/php.md b/docs/SDKs/php.md
index 69ad930..fb637aa 100644
--- a/docs/SDKs/php.md
+++ b/docs/SDKs/php.md
@@ -487,7 +487,7 @@ echo "Document ID: {$result->documentId}\n";
`sendSignature` can also schedule automatic reminder emails and an expiration deadline. All eight
schedule fields are optional and **both features are off by default**, so omitting them preserves
-the original send behavior. The resolved schedule is **frozen onto the document when it is sent** —
+the original send behavior. The resolved schedule is **frozen onto the document when it is sent**:
changing your org defaults later never alters a document already out for signature.
```php
@@ -517,7 +517,7 @@ deadline is readable afterwards via `getStatus()->expiresAt`.
Send a standalone reminder to whoever's turn it is to sign
(`POST /turbosign/documents/{documentId}/send-reminder`). It is independent of the automatic
-cadence — it works even when reminders are disabled or the per-signer cap is already spent, does
+cadence: it works even when reminders are disabled or the per-signer cap is already spent, does
**not** consume that cap, and only emails signers at the *current* signing order. Pass `null` (or
omit the argument) to remind everyone eligible; do not pass an empty array, which the API rejects.
@@ -530,7 +530,7 @@ foreach ($result['results'] as $r) {
echo "{$r['recipientId']}: {$r['status']}\n";
}
-// Or limit to specific recipients — all-or-nothing: every id must be a current-order pending signer.
+// Or limit to specific recipients (all-or-nothing): every id must be a current-order pending signer.
TurboSign::sendReminder('document-uuid', ['recipient-uuid-1', 'recipient-uuid-2']);
```
@@ -566,7 +566,7 @@ foreach ($progress->recipients as $r) {
:::tip Two status fields, and they differ on purpose
`status` is the raw database value and is only ever `pending`, `viewed` or `completed`.
-`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired` — that
+`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired`: that
is the one to display.
On a voided or expired document an unsigned signer still reads `pending` in `status`, so
@@ -579,14 +579,14 @@ the document is terminal.
:::
-Each recipient also carries a `delivery` block — `firstSentOn`, `lastSentOn`, `totalSent`,
+Each recipient also carries a `delivery` block: `firstSentOn`, `lastSentOn`, `totalSent`,
`reminderCount`, `lastRemindedAt`, `warningCount`, `lastWarningAt`. It counts the signature
request, resends, reminders, expiry warnings and terminal notices; CC notifications are
excluded, since a CC address is not a signer.
:::warning `reminderCount` and `lastRemindedAt` do not mean what their names suggest
-`reminderCount` counts **automatic (scheduled) reminders only** — the counter `maxReminders`
+`reminderCount` counts **automatic (scheduled) reminders only**: the counter `maxReminders`
caps. A manual "remind now" is a standalone nudge that must not consume the cap budget, so it
does **not** increment this, even though the email it sends *does* appear in `totalSent`.
@@ -595,7 +595,7 @@ signature-request send, each scheduled reminder, each manual "remind now" and ea
warning all stamp it. Only scheduled reminders bump `reminderCount`.
So a freshly-sent document returns a non-null `lastRemindedAt` equal to the invitation
-timestamp alongside `reminderCount: 0` — nobody has been reminded. To answer "have we actually
+timestamp alongside `reminderCount: 0`: nobody has been reminded. To answer "have we actually
chased this person", read `totalSent`, not `reminderCount`.
`warningCount` / `lastWarningAt` have no such caveat.
@@ -783,7 +783,7 @@ use TurboDocx\Types\FieldConditional;
use TurboDocx\Types\ConditionalOperator;
use TurboDocx\Types\ConditionalAction;
-// Controlling checkbox — carries a stable fieldKey
+// Controlling checkbox, carries a stable fieldKey
new Field(
type: SignatureFieldType::CHECKBOX,
recipientEmail: 'reviewer@company.com',
@@ -795,7 +795,7 @@ new Field(
metadata: new FieldMetadata(fieldKey: 'request_changes')
);
-// Dependent text field — hidden until the checkbox is checked
+// Dependent text field, hidden until the checkbox is checked
new Field(
type: SignatureFieldType::TEXT,
recipientEmail: 'reviewer@company.com',
@@ -823,12 +823,14 @@ malformed rule returns `400 InvalidConditionalRule`; a well-formed rule whose
## Error Handling
-The SDK provides typed exceptions for different error scenarios:
+Every typed exception extends `TurboDocxException`, itself a plain `Exception` subclass with two extra readonly properties: `statusCode` (int, HTTP status) and `errorCode` (string, e.g. `'VALIDATION_ERROR'`). Because the constructor hardcodes PHP's built-in `Exception::getCode()` to `0`, read `$e->errorCode`, not `$e->getCode()`, for the machine-readable reason:
```php
use TurboDocx\Exceptions\AuthenticationException;
+use TurboDocx\Exceptions\AuthorizationException;
use TurboDocx\Exceptions\ValidationException;
use TurboDocx\Exceptions\NotFoundException;
+use TurboDocx\Exceptions\ConflictException;
use TurboDocx\Exceptions\RateLimitException;
use TurboDocx\Exceptions\NetworkException;
@@ -837,18 +839,27 @@ try {
} catch (AuthenticationException $e) {
// 401 - Invalid API key or access token
echo "Authentication failed: {$e->getMessage()}\n";
+} catch (AuthorizationException $e) {
+ // 403 - Valid credentials without permission for this operation
+ echo "Authorization error: {$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 (ConflictException $e) {
+ // 409 - Conflicts with the current resource state
+ 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";
+} catch (TurboDocxException $e) {
+ // Catch-all: read the machine-readable reason from errorCode, not getCode()
+ echo "Error {$e->errorCode}: {$e->getMessage()} (status {$e->statusCode})\n";
}
```
@@ -858,16 +869,18 @@ try {
| ------------------------- | ----------- | ---------------------------------- |
| `TurboDocxException` | varies | Base exception for all SDK errors |
| `AuthenticationException` | 401 | Invalid or missing API credentials |
+| `AuthorizationException` | 403 | Valid credentials without permission for this operation |
| `ValidationException` | 400 | Invalid request parameters |
| `NotFoundException` | 404 | Document or resource not found |
+| `ConflictException` | 409 | Request conflicts with current resource state |
| `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')
+- `statusCode` - HTTP status code, a public readonly `?int` (null for `NetworkException`)
+- `errorCode` - Error code string (e.g., `'AUTHENTICATION_ERROR'`), a public readonly `?string`
---
@@ -912,13 +925,13 @@ enum DocumentStatus: string {
case VOIDED = 'voided';
}
-// Conditional (IF/THEN) operator — the condition evaluated against the controlling checkbox
+// Conditional (IF/THEN) operator, the condition evaluated against the controlling checkbox
enum ConditionalOperator: string {
case IS_CHECKED = 'is_checked';
case IS_NOT_CHECKED = 'is_not_checked';
}
-// Conditional (IF/THEN) action — what happens to the dependent field until the condition is met
+// Conditional (IF/THEN) action, what happens to the dependent field until the condition is met
enum ConditionalAction: string {
case SHOW = 'show'; // hidden until met
case UNLOCK = 'unlock'; // visible but read-only until met
diff --git a/docs/SDKs/python.md b/docs/SDKs/python.md
index 1461c18..084043b 100644
--- a/docs/SDKs/python.md
+++ b/docs/SDKs/python.md
@@ -88,7 +88,7 @@ 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.
+`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.
:::
---
@@ -424,7 +424,7 @@ for r in result["recipients"]:
:::tip Two status fields, and they differ on purpose
`status` is the raw database value and is only ever `pending`, `viewed` or `completed`.
-`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired` — that
+`effectiveStatus` layers the document's outcome on top, adding `voided` and `expired`: that
is the one to display.
On a voided or expired document an unsigned signer still reads `pending` in `status`, so
@@ -437,14 +437,14 @@ the document is terminal.
:::
-Each recipient also carries a `delivery` block — `firstSentOn`, `lastSentOn`, `totalSent`,
+Each recipient also carries a `delivery` block: `firstSentOn`, `lastSentOn`, `totalSent`,
`reminderCount`, `lastRemindedAt`, `warningCount`, `lastWarningAt`. It counts the signature
request, resends, reminders, expiry warnings and terminal notices; CC notifications are
excluded, since a CC address is not a signer.
:::warning `reminderCount` and `lastRemindedAt` do not mean what their names suggest
-`reminderCount` counts **automatic (scheduled) reminders only** — the counter `maxReminders`
+`reminderCount` counts **automatic (scheduled) reminders only**: the counter `maxReminders`
caps. A manual "remind now" is a standalone nudge that must not consume the cap budget, so it
does **not** increment this, even though the email it sends *does* appear in `totalSent`.
@@ -453,7 +453,7 @@ signature-request send, each scheduled reminder, each manual "remind now" and ea
warning all stamp it. Only scheduled reminders bump `reminderCount`.
So a freshly-sent document returns a non-null `lastRemindedAt` equal to the invitation
-timestamp alongside `reminderCount: 0` — nobody has been reminded. To answer "have we actually
+timestamp alongside `reminderCount: 0`: nobody has been reminded. To answer "have we actually
chased this person", read `totalSent`, not `reminderCount`.
`warningCount` / `lastWarningAt` have no such caveat.
@@ -491,7 +491,7 @@ result = await TurboSign.resend_email("document-uuid", recipient_ids=["recipient
### Send reminder
Send a standalone reminder (`POST /turbosign/documents/:id/send-reminder`) to whoever's turn it
-is to sign. It is independent of the automatic reminder cadence — it works even when reminders
+is to sign. It is independent of the automatic reminder cadence: it works even when reminders
are disabled or the per-signer `max_reminders` cap is already spent, does **not** consume that
cap, and only emails signers at the *current* signing order. Omit `recipient_ids` to remind
everyone eligible; do not pass an empty list, which the API rejects.
@@ -522,7 +522,7 @@ 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.
+Every error is a plain `Exception` subclass; catch the most specific one first, since `except TurboDocxError` also matches every subclass below it. Each of the 7 named subclasses sets its own `DEFAULT_CODE` class attribute, so `e.code` is populated for those even when the API response itself carries none; the base `TurboDocxError` raised for an unmapped status (e.g. an unexpected 5xx) has `DEFAULT_CODE = None`, so `e.code` can be `None` there.
### Error Classes
@@ -597,13 +597,13 @@ asyncio.run(send_with_error_handling())
### Error Properties
-All errors include these properties:
+All errors include these instance attributes:
| 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 |
+| `code` | `str \| None` | Machine-readable error code; the API's code wins when present, otherwise the class's `DEFAULT_CODE` |
---
@@ -679,7 +679,7 @@ Field configuration supporting both coordinate-based and template-based position
| `required` | `bool` | No | Whether field is required |
| `backgroundColor` | `str` | No | Background color (hex, rgb, or named) |
| `template` | `Dict` | No | Template anchor configuration |
-| `metadata` | `Dict` | No | Conditional (IF/THEN) metadata — see below |
+| `metadata` | `Dict` | No | Conditional (IF/THEN) metadata, see below |
\*Required when not using template anchors
@@ -769,13 +769,13 @@ Request configuration for `create_signature_review_link` and `send_signature` me
| `sender_email` | `str` | No\*\* | Sender / reply-to email (overrides the configured value) |
| `cc_emails` | `List[str]` | No | Array of CC email addresses |
| `reminders_enabled` | `bool` | No | Send reminder emails to signers who haven't signed. Off by default |
-| `reminder_delay` | `Dict` | No | `{"value": N, "unit": "days"\|"hours"}` — time to the FIRST reminder |
-| `reminder_interval` | `Dict` | No | `{"value": N, "unit": ...}` — gap between later reminders |
+| `reminder_delay` | `Dict` | No | `{"value": N, "unit": "days"\|"hours"}`, time to the FIRST reminder |
+| `reminder_interval` | `Dict` | No | `{"value": N, "unit": ...}`, gap between later reminders |
| `max_reminders` | `int` | No | Cap per signer. `-1` unlimited, `0` none, max `50`. Default `5` |
| `expiration_enabled` | `bool` | No | Close the signing window after `expire_after`. Off by default |
-| `expire_after` | `Dict` | No | `{"value": N, "unit": ...}` — how long the document stays signable |
-| `expiration_warning` | `Dict` | No | `{"value": N, "unit": ...}` — how far before expiry warnings start. `0` = never warn |
-| `expiration_warning_interval` | `Dict` | No | `{"value": N, "unit": ...}` — gap between warnings once they start |
+| `expire_after` | `Dict` | No | `{"value": N, "unit": ...}`, how long the document stays signable |
+| `expiration_warning` | `Dict` | No | `{"value": N, "unit": ...}`, how far before expiry warnings start. `0` = never warn |
+| `expiration_warning_interval` | `Dict` | No | `{"value": N, "unit": ...}`, gap between warnings once they start |
:::info Duration bounds
Each duration `{"value", "unit"}` uses `"days"` or `"hours"`; `value` is a whole number from `1`
diff --git a/docs/SDKs/quote-go.md b/docs/SDKs/quote-go.md
index 7c094a4..4560bd7 100644
--- a/docs/SDKs/quote-go.md
+++ b/docs/SDKs/quote-go.md
@@ -2,7 +2,7 @@
title: TurboQuote Go SDK
sidebar_position: 22
sidebar_label: "TurboQuote: Go"
-description: Official TurboDocx TurboQuote SDK for Go. Create and send quotes, manage line items, products, bundles, price books, companies, contacts, and quote templates programmatically with idiomatic Go and full context support.
+description: "Go TurboQuote SDK: create and send quotes, manage line items, products, bundles, price books, companies, and contacts."
keywords:
- turboquote go
- turboquote sdk golang
diff --git a/docs/SDKs/quote-java.md b/docs/SDKs/quote-java.md
index e300d2a..7368912 100644
--- a/docs/SDKs/quote-java.md
+++ b/docs/SDKs/quote-java.md
@@ -2,7 +2,7 @@
title: TurboQuote Java SDK
sidebar_position: 21
sidebar_label: "TurboQuote: Java"
-description: Official TurboDocx TurboQuote SDK for Java. Create and send quotes, manage line items, products, bundles, and price books programmatically with full CPQ lifecycle support.
+description: "Java TurboQuote SDK: create and send quotes, manage line items, products, bundles, and price books with full CPQ support."
keywords:
- turboquote java
- quote sdk java
@@ -41,7 +41,7 @@ TurboQuote is TurboDocx's CPQ (Configure, Price, Quote) module. Build a product
com.turbodocx
turbodocx-sdk
- 0.5.0
+ 0.7.0
```
@@ -49,14 +49,14 @@ TurboQuote is TurboDocx's CPQ (Configure, Price, Quote) module. Build a product
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.5.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
```
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.5.0")
+implementation("com.turbodocx:turbodocx-sdk:0.7.0")
```
diff --git a/docs/SDKs/quote-javascript.md b/docs/SDKs/quote-javascript.md
index b78bdae..20d5af2 100644
--- a/docs/SDKs/quote-javascript.md
+++ b/docs/SDKs/quote-javascript.md
@@ -2,7 +2,7 @@
title: TurboQuote JavaScript / TypeScript SDK
sidebar_position: 20
sidebar_label: "TurboQuote: JavaScript / TypeScript"
-description: Official TurboDocx TurboQuote SDK for JavaScript and TypeScript. Create quotes and proposals, manage line items, products, bundles, price books, companies, and contacts — all with full TypeScript types and async/await patterns.
+description: "JavaScript/TypeScript TurboQuote SDK: create quotes, manage line items, products, bundles, price books, companies, contacts."
keywords:
- turboquote javascript
- turboquote typescript
@@ -98,7 +98,7 @@ TurboQuote.configure({
:::tip No senderEmail on the client — but set one on your quote template
Unlike TurboSign, `TurboQuote.configure()` does **not** require `senderEmail` or `senderName` — quotes are not sent as signature emails. Only a credential is required — either `apiKey` or an OAuth `accessToken` (`accessToken` wins when both are set); `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.
-The quote's **"Prepared by"** sender comes from your **org quote template** instead. Because an API key has no mailbox of its own, every sender-resolving call — `createQuote`, `duplicateQuote`, `sendQuote` / `sendQuoteWithDeliverable`, and `handleExpiredQuote` — fails with `400 SenderEmailRequired` when the org's quote template has no sender email set. A companion `400 SenderNameRequired` is returned when no sender **name** resolves. Configure both **Sender Name** and **Sender Email** once (`TurboQuote.updateTemplate({ senderEmail, senderName })`) and all of them resolve cleanly.
+The quote's **"Prepared by"** sender comes from your **org quote template** instead. Because an API key has no mailbox of its own, if the org's quote template has no sender email set, `createQuote`, `duplicateQuote`, `sendQuote` / `sendQuoteWithDeliverable`, and `handleExpiredQuote` still succeed: they fall back to a generic TurboDocx sender (`no-reply@turbodocx.com`) rather than rejecting the call. Configure both **Sender Name** and **Sender Email** once (`const tmpl = await TurboQuote.getTemplate(); await TurboQuote.updateTemplate(tmpl.id, { senderEmail, senderName });`) so quotes show your own sender identity instead of the generic fallback.
:::
### Environment Variables
@@ -436,7 +436,6 @@ specific error `code` before anything is created or emailed:
| No line items | `QuoteHasNoLineItems` |
| Contact missing a name or email | `QuoteContactRequired` |
| Company or contact deleted/deactivated | `QuoteCustomerInactive` |
-| No sender email resolvable (API-key callers) | `SenderEmailRequired` |
A quote with **no line items cannot be sent** — add at least one product, bundle, or custom
line item first. Likewise an **expired quote is rejected**; update `validUntil`, or use the
diff --git a/docs/SDKs/quote-php.md b/docs/SDKs/quote-php.md
index 6035da8..162c824 100644
--- a/docs/SDKs/quote-php.md
+++ b/docs/SDKs/quote-php.md
@@ -2,7 +2,7 @@
title: TurboQuote PHP SDK
sidebar_position: 16
sidebar_label: "TurboQuote: PHP"
-description: Official TurboDocx TurboQuote SDK for PHP. Create, manage, and send quotes/proposals with full CPQ capabilities — line items, products, bundles, price books, companies, contacts, and quote templates, all from PHP 8.1+.
+description: "PHP TurboQuote SDK: create, manage, and send quotes with line items, products, bundles, price books, companies, and contacts."
keywords:
- turboquote php
- quote sdk php
diff --git a/docs/SDKs/quote-python.md b/docs/SDKs/quote-python.md
index 70e22b1..9b298eb 100644
--- a/docs/SDKs/quote-python.md
+++ b/docs/SDKs/quote-python.md
@@ -2,7 +2,7 @@
title: TurboQuote Python SDK
sidebar_position: 20
sidebar_label: "TurboQuote: Python"
-description: Official TurboDocx TurboQuote SDK for Python. Create, manage, and send quotes/proposals with full CPQ capabilities — line items, products, bundles, price books, companies, contacts, and quote templates, all via async Python 3.9+.
+description: "Python TurboQuote SDK: create, manage, and send quotes with line items, products, bundles, and price books. Async, Python 3.9+."
keywords:
- turboquote python
- quote sdk python
diff --git a/docs/SDKs/webhooks-go.md b/docs/SDKs/webhooks-go.md
index ea844b5..2e27bbb 100644
--- a/docs/SDKs/webhooks-go.md
+++ b/docs/SDKs/webhooks-go.md
@@ -2,7 +2,7 @@
title: TurboWebhooks Go SDK
sidebar_position: 18
sidebar_label: "TurboWebhooks: Go"
-description: Official TurboDocx Webhooks SDK for Go. Subscribe to all seven TurboSign signature events with the typed WebhookEvent constants, verify inbound webhook signatures with HMAC-SHA256, and manage delivery history programmatically.
+description: "Go TurboWebhooks SDK: subscribe to all seven TurboSign events, verify HMAC-SHA256 signatures, manage delivery history."
keywords:
- turbodocx webhooks
- turbowebhooks go
diff --git a/docs/SDKs/webhooks-java.md b/docs/SDKs/webhooks-java.md
index 0b122f4..2691ff9 100644
--- a/docs/SDKs/webhooks-java.md
+++ b/docs/SDKs/webhooks-java.md
@@ -2,7 +2,7 @@
title: TurboWebhooks Java SDK
sidebar_position: 19
sidebar_label: "TurboWebhooks: Java"
-description: Official TurboDocx Webhooks SDK for Java. Subscribe to all seven TurboSign signature events with the WebhookEvent enum, verify inbound webhook signatures with HMAC-SHA256, and manage delivery history programmatically.
+description: "Java TurboWebhooks SDK: subscribe to all seven TurboSign events, verify HMAC-SHA256 signatures, manage delivery history."
keywords:
- turbodocx webhooks
- turbowebhooks java
@@ -45,7 +45,7 @@ For the full conceptual overview of how webhooks work in TurboSign (delivery ret
com.turbodocx
turbodocx-sdk
- 0.5.0
+ 0.7.0
```
@@ -53,14 +53,14 @@ For the full conceptual overview of how webhooks work in TurboSign (delivery ret
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.5.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
```
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.5.0")
+implementation("com.turbodocx:turbodocx-sdk:0.7.0")
```
diff --git a/docs/SDKs/webhooks-javascript.md b/docs/SDKs/webhooks-javascript.md
index 85708d0..f54ca6d 100644
--- a/docs/SDKs/webhooks-javascript.md
+++ b/docs/SDKs/webhooks-javascript.md
@@ -2,7 +2,7 @@
title: TurboWebhooks JavaScript / TypeScript SDK
sidebar_position: 16
sidebar_label: "TurboWebhooks: JavaScript"
-description: Official TurboDocx Webhooks SDK for JavaScript and TypeScript. Subscribe to all seven TurboSign signature events with the typed WebhookEvents constants, verify inbound webhook signatures with HMAC-SHA256, and manage delivery history programmatically.
+description: "JavaScript/TypeScript TurboWebhooks SDK: subscribe to TurboSign events, verify HMAC-SHA256 signatures, manage delivery history."
keywords:
- turbodocx webhooks
- turbowebhooks javascript
diff --git a/docs/SDKs/webhooks-php.md b/docs/SDKs/webhooks-php.md
index b7ba7ce..d728f72 100644
--- a/docs/SDKs/webhooks-php.md
+++ b/docs/SDKs/webhooks-php.md
@@ -2,7 +2,7 @@
title: TurboWebhooks PHP SDK
sidebar_position: 15
sidebar_label: "TurboWebhooks: PHP"
-description: Official TurboDocx Webhooks SDK for PHP. Subscribe to all seven TurboSign signature events with the WebhookEvent backed enum, verify inbound webhook signatures with HMAC-SHA256, and manage delivery history programmatically.
+description: "PHP TurboWebhooks SDK: subscribe to all seven TurboSign events, verify HMAC-SHA256 signatures, manage delivery history."
keywords:
- turbodocx webhooks
- turbowebhooks php
diff --git a/docs/SDKs/webhooks-python.md b/docs/SDKs/webhooks-python.md
index 130e457..a96c1d6 100644
--- a/docs/SDKs/webhooks-python.md
+++ b/docs/SDKs/webhooks-python.md
@@ -2,7 +2,7 @@
title: TurboWebhooks Python SDK
sidebar_position: 17
sidebar_label: "TurboWebhooks: Python"
-description: Official TurboDocx Webhooks SDK for Python. Subscribe to all seven TurboSign signature events with the WEBHOOK_EVENT_* constants, verify inbound webhook signatures with HMAC-SHA256, and manage delivery history programmatically.
+description: "Python TurboWebhooks SDK: subscribe to all seven TurboSign events, verify HMAC-SHA256 signatures, manage delivery history."
keywords:
- turbodocx webhooks
- turbowebhooks python