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