TurboSign Python SDK
Let an agent scaffold this for you
Install the TurboDocx Quickstart Skill and let Claude Code, Cursor, Copilot, Codex, or any agent that speaks the Agent Skills standard install the SDK, wire it into your app, and write a working TurboSign integration end-to-end.
$npx skills add TurboDocx/quickstart›/turbodocx-sdk turbosignThe official TurboDocx SDK for Python applications. Build document generation and digital signature workflows with async/await patterns and comprehensive error handling. Available on PyPI as turbodocx-sdk.
Installation
- pip
- Poetry
- Pipenv
pip install turbodocx-sdk
poetry add turbodocx-sdk
pipenv install turbodocx-sdk
Requirements
- Python 3.9+
httpx(installed automatically)
Configuration
from turbodocx_sdk import TurboSign
import os
# Configure globally (recommended)
TurboSign.configure(
api_key=os.environ["TURBODOCX_API_KEY"], # Required: Your TurboDocx API key
org_id=os.environ["TURBODOCX_ORG_ID"], # Required: Your organization ID
sender_email="contracts@yourcompany.com", # Required: Reply-to address for signature emails
sender_name="Your Company", # Recommended: Sender name shown in emails
# base_url="https://api.turbodocx.com" # Optional: Override base URL
)
Authenticate using api_key. API keys are recommended for server-side applications.
Environment Variables
# .env
TURBODOCX_API_KEY=your_api_key_here
TURBODOCX_ORG_ID=your_org_id_here
TURBODOCX_SENDER_EMAIL=contracts@yourcompany.com
TURBODOCX_SENDER_NAME=Your Company
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 steps from the SDKs main page.
Quick Start
Send a Document for Signature
import asyncio
import json
import os
from turbodocx_sdk import TurboSign
TurboSign.configure(
api_key=os.environ["TURBODOCX_API_KEY"],
org_id=os.environ["TURBODOCX_ORG_ID"],
sender_email="contracts@acme.com",
sender_name="Acme Corp",
)
async def send_contract():
result = await TurboSign.send_signature(
recipients=[
{"name": "Alice Smith", "email": "alice@example.com", "signingOrder": 1},
{"name": "Bob Johnson", "email": "bob@example.com", "signingOrder": 2}
],
fields=[
# Alice's signature
{"type": "signature", "page": 1, "x": 100, "y": 650, "width": 200, "height": 50, "recipientEmail": "alice@example.com"},
{"type": "date", "page": 1, "x": 320, "y": 650, "width": 100, "height": 30, "recipientEmail": "alice@example.com"},
# Bob's signature
{"type": "signature", "page": 1, "x": 100, "y": 720, "width": 200, "height": 50, "recipientEmail": "bob@example.com"},
{"type": "date", "page": 1, "x": 320, "y": 720, "width": 100, "height": 30, "recipientEmail": "bob@example.com"}
],
file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
document_name="Service Agreement",
sender_name="Acme Corp",
sender_email="contracts@acme.com",
)
print("Result:", json.dumps(result, indent=2))
asyncio.run(send_contract())
Using Template-Based Fields
import asyncio
import json
async def send_with_template():
result = await TurboSign.send_signature(
recipients=[{"name": "Alice Smith", "email": "alice@example.com", "signingOrder": 1}],
fields=[
{
"type": "signature",
"recipientEmail": "alice@example.com",
"template": {
"anchor": "{SIGNATURE_ALICE}",
"placement": "replace",
"size": {"width": 200, "height": 50},
},
},
{
"type": "date",
"recipientEmail": "alice@example.com",
# Pins a fixed date in MM/DD/YYYY; omit to auto-fill the signing date
"defaultValue": "12/31/2026",
"template": {
"anchor": "{DATE_ALICE}",
"placement": "replace",
"size": {"width": 100, "height": 30},
},
},
],
file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
)
print("Result:", json.dumps(result, indent=2))
asyncio.run(send_with_template())
Important: The document file must contain the anchor text (e.g., {SIGNATURE_ALICE}, {DATE_ALICE}) that you reference in your fields. If the anchors don't exist in the document, the API will return an error.
File Input Methods
TurboSign supports four different ways to provide document files:
The examples below use await, so they must run inside an async function (call them with asyncio.run(...)). See the Send a Document for Signature Quick Start for the full runnable form.
1. File Upload (bytes)
with open("./contract.pdf", "rb") as f:
pdf_buffer = f.read()
result = await TurboSign.send_signature(
file=pdf_buffer,
recipients=[
{"name": "John Doe", "email": "john@example.com", "signingOrder": 1},
],
fields=[
{
"type": "signature",
"page": 1,
"x": 100,
"y": 650,
"width": 200,
"height": 50,
"recipientEmail": "john@example.com",
},
],
)
2. File URL (file_link)
result = await TurboSign.send_signature(
file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
recipients=[
{"name": "John Doe", "email": "john@example.com", "signingOrder": 1},
],
fields=[
{
"type": "signature",
"page": 1,
"x": 100,
"y": 650,
"width": 200,
"height": 50,
"recipientEmail": "john@example.com",
},
],
)
Use file_link when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files.
3. TurboDocx Deliverable ID
# Use a previously generated TurboDocx document
result = await TurboSign.send_signature(
deliverable_id="deliverable-uuid-from-turbodocx",
recipients=[
{"name": "John Doe", "email": "john@example.com", "signingOrder": 1},
],
fields=[
{
"type": "signature",
"page": 1,
"x": 100,
"y": 650,
"width": 200,
"height": 50,
"recipientEmail": "john@example.com",
},
],
)
deliverable_id references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign.
4. TurboDocx Template ID
# Use a pre-configured TurboSign template
result = await TurboSign.send_signature(
template_id="template-uuid-from-turbodocx", # Template already contains anchors
recipients=[
{"name": "Alice Smith", "email": "alice@example.com", "signingOrder": 1},
],
fields=[
{
"type": "signature",
"recipientEmail": "alice@example.com",
"template": {
"anchor": "{SIGNATURE_ALICE}",
"placement": "replace",
"size": {"width": 200, "height": 50},
},
},
],
)
template_id references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents.
API Reference
The snippets below use await, so they must run inside an async function. For a standalone script, wrap the call and run it with asyncio.run(...), and add import json if the snippet calls json.dumps:
import asyncio
import json
async def main():
result = await TurboSign.get_status("document-uuid")
print(json.dumps(result, indent=2))
asyncio.run(main())
Configure
Configure the SDK with your API credentials and organization settings.
TurboSign.configure(
api_key: str, # Required: Your TurboDocx API key
org_id: str, # Required: Your organization ID
sender_email: str, # Required: Reply-to address for signature emails
sender_name: str = None, # Recommended: Sender name shown in emails
base_url: str = "https://api.turbodocx.com" # Optional: API base URL
)
Prepare for review
Upload a document for preview without sending signature request emails.
result = await TurboSign.create_signature_review_link(
recipients=[{"name": "John Doe", "email": "john@example.com", "signingOrder": 1}],
fields=[{"type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "john@example.com"}],
file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
document_name="Contract Draft",
)
print(result["documentId"])
print(result["previewUrl"])
Prepare for signing
Upload a document and immediately send signature requests to all recipients.
result = await TurboSign.send_signature(
recipients=[{"name": "Recipient Name", "email": "recipient@example.com", "signingOrder": 1}],
fields=[{"type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "recipient@example.com"}],
file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
document_name="Service Agreement",
sender_name="Your Company",
sender_email="sender@company.com",
)
print(result["documentId"])
Schedule reminders and expiration
send_signature() also accepts an optional reminder + expiration schedule. Both features are
off by default, so omitting these kwargs preserves the original send behavior. The resolved
deadline is frozen onto the document at send time and is then readable via
get_status()["expiresAt"].
result = await TurboSign.send_signature(
# ...recipients, fields, file source, etc.
reminders_enabled=True,
reminder_delay={"value": 2, "unit": "days"}, # time to the FIRST reminder
reminder_interval={"value": 3, "unit": "days"}, # gap between later reminders
max_reminders=5, # -1 unlimited, 0 none, max 50
expiration_enabled=True,
expire_after={"value": 14, "unit": "days"}, # how long the document stays signable
expiration_warning={"value": 1, "unit": "days"}, # 0 = never warn
expiration_warning_interval={"value": 1, "unit": "days"},
)
Durations are {"value": N, "unit": "days" | "hours"} objects. value is a whole number from
1 up to a maximum of 999 days / 23976 hours; max_reminders accepts -1 (unlimited),
0 (none), or up to 50, defaulting to 5; expiration_warning may be 0 to disable
warnings. See the full field reference under Request Parameters.
Get status
Retrieve the document-level status. For per-signer detail, use Get recipients.
result = await TurboSign.get_status("document-uuid")
print("Result:", json.dumps(result, indent=2))
# status is 'under_review', 'completed', 'voided', or the terminal 'expired'
print("Status:", result["status"])
# expiresAt is the signing-window deadline (ISO 8601), or None when expiration is off.
print("Expires:", result.get("expiresAt"))
Once a document's deadline passes it moves to the terminal expired status and its signing
links stop working. The same expiresAt deadline is also returned on the document object from
get_recipients() (result["document"]["expiresAt"]).
Get recipients
See who the document went to, who has signed, who you are still waiting on, and who sent it.
result = await TurboSign.get_recipients("document-uuid")
summary = result["summary"]
print(f"{summary['completed']}/{summary['total']} signed, waiting on {summary['waitingOn']}")
for r in result["recipients"]:
print(f"{r['name']} <{r['email']}>: {r['effectiveStatus']}")
print(f" emailed {r['delivery']['totalSent']}x")
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
is the one to display.
On a voided or expired document an unsigned signer still reads pending in status, so
branching on it would show someone as "still to sign" when their signing link is already dead.
A completed signature is never revoked: someone who signed before the document was voided
still reads completed.
summary counts by effectiveStatus, and waitingOn (pending + viewed) drops to zero once
the document is terminal.
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.
reminderCount and lastRemindedAt do not mean what their names suggestreminderCount 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.
lastRemindedAt is a cadence clock, not a record of a reminder: the initial
signature-request send, each scheduled reminder, each manual "remind now" and each expiry
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
chased this person", read totalSent, not reminderCount.
warningCount / lastWarningAt have no such caveat.
Download document
Download the completed signed document as PDF bytes.
pdf_bytes = await TurboSign.download("document-uuid")
# Save to file
with open("signed-contract.pdf", "wb") as f:
f.write(pdf_bytes)
Void
Cancel/void a signature request.
result = await TurboSign.void_document("document-uuid", reason="Contract terms changed")
Resend
Resend signature request emails to specific recipients.
result = await TurboSign.resend_email("document-uuid", recipient_ids=["recipient-uuid-1", "recipient-uuid-2"])
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
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.
# Remind everyone whose turn it is:
result = await TurboSign.send_reminder("document-uuid")
# Or limit to specific recipients:
result = await TurboSign.send_reminder("document-uuid", recipient_ids=["recipient-uuid-1"])
for entry in result["results"]:
# status is e.g. 'sent' or 'skipped_wrong_order'
print(f"{entry['recipientId']}: {entry['status']}")
Get audit trail
Retrieve the complete audit trail for a document, including all events and actions.
result = await TurboSign.get_audit_trail("document-uuid")
print("Result:", json.dumps(result, indent=2))
Error Handling
The SDK provides typed error classes for different failure scenarios. All errors extend the base TurboDocxError class.
Error Classes
| Error Class | Status Code | Description |
|---|---|---|
TurboDocxError | varies | Base error class for all SDK errors |
AuthenticationError | 401 | Invalid or missing API credentials |
AuthorizationError | 403 | Authenticated but lacks required permissions |
ValidationError | 400 | Invalid request parameters |
NotFoundError | 404 | Document or resource not found |
ConflictError | 409 | Request conflicts with current resource state |
RateLimitError | 429 | Too many requests |
NetworkError | - | Network connectivity issues |
Handling Errors
import asyncio
from turbodocx_sdk import (
TurboSign,
TurboDocxError,
AuthenticationError,
AuthorizationError,
ValidationError,
NotFoundError,
ConflictError,
RateLimitError,
NetworkError,
)
async def send_with_error_handling():
try:
result = await TurboSign.send_signature(
recipients=[{"name": "John Doe", "email": "john@example.com", "signingOrder": 1}],
fields=[{
"type": "signature",
"page": 1,
"x": 100,
"y": 650,
"width": 200,
"height": 50,
"recipientEmail": "john@example.com",
}],
file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
)
except AuthenticationError as e:
print(f"Authentication failed: {e}")
# Check your API key and org ID
except AuthorizationError as e:
print(f"Not authorized: {e}")
# Authenticated, but lacks permission for this operation
except ValidationError as e:
print(f"Validation error: {e}")
# Check request parameters
except NotFoundError as e:
print(f"Resource not found: {e}")
# Document or recipient doesn't exist
except ConflictError as e:
print(f"Conflict: {e}")
# Request conflicts with the current resource state
except RateLimitError as e:
print(f"Rate limited: {e}")
# Wait and retry
except NetworkError as e:
print(f"Network error: {e}")
# Check connectivity
except TurboDocxError as e:
print(f"SDK error: {e}, status_code={e.status_code}, code={e.code}")
asyncio.run(send_with_error_handling())
Error Properties
All errors include these properties:
| Property | Type | Description |
|---|---|---|
message | str | Human-readable error description (via str(error)) |
status_code | int | None | HTTP status code (if applicable) |
code | str | None | Machine-readable error code |
Python Types
The SDK uses Python type hints with Dict[str, Any] for flexible JSON-like structures.
Importing Types
from typing import Dict, List, Any, Optional
SignatureFieldType
String literal values for field types:
# Available field type values
field_types = [
"signature",
"initial",
"date",
"text",
"full_name",
"title",
"company",
"first_name",
"last_name",
"email",
"checkbox",
]
Recipient
Recipient configuration for signature requests:
| Property | Type | Required | Description |
|---|---|---|---|
name | str | Yes | Recipient's full name |
email | str | Yes | Recipient's email address |
signingOrder | int | Yes | Signing order (1-indexed) |
recipient: Dict[str, Any] = {
"name": "John Doe",
"email": "john@example.com",
"signingOrder": 1
}
Field
Field configuration supporting both coordinate-based and template-based positioning:
| Property | Type | Required | Description |
|---|---|---|---|
type | str | Yes | Field type (see SignatureFieldType) |
recipientEmail | str | Yes | Which recipient fills this field |
page | int | No* | Page number (1-indexed) |
x | int | No* | X coordinate in pixels |
y | int | No* | Y coordinate in pixels |
width | int | No* | Field width in pixels |
height | int | No* | Field height in pixels |
defaultValue | str | No | Default value (checkbox: "true"/"false"; date: a fixed MM/DD/YYYY, omit to auto-fill the signing date) |
isMultiline | bool | No | Enable multiline text |
isReadonly | bool | No | Make field read-only (pre-filled) |
required | bool | No | Whether field is required |
backgroundColor | str | No | Background color (hex, rgb, or named) |
template | Dict | No | Template anchor configuration |
metadata | Dict | No | Conditional (IF/THEN) metadata — see below |
*Required when not using template anchors
Metadata Configuration (Conditional Fields):
The optional metadata dict 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 | str | No | Stable id on a controlling checkbox (type: "checkbox"). |
conditional | Dict | No | Rule on a dependent field (see below). |
conditional.controllingFieldKey | str | Yes | The controlling checkbox's fieldKey. Must be non-empty. |
conditional.operator | str | Yes | "is_checked" | "is_not_checked". |
conditional.action | str | Yes | "show" (hidden until met) | "unlock" (locked until met). |
# Checkbox reveals a text field when checked
fields = [
{
"type": "checkbox",
"recipientEmail": "reviewer@company.com",
"page": 1, "x": 100, "y": 400, "width": 20, "height": 20,
"metadata": {"fieldKey": "request_changes"},
},
{
"type": "text",
"recipientEmail": "reviewer@company.com",
"page": 1, "x": 130, "y": 400, "width": 300, "height": 60,
"metadata": {
"conditional": {
"controllingFieldKey": "request_changes",
"operator": "is_checked",
"action": "show",
}
},
},
]
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.
Template Configuration:
| Property | Type | Required | Description |
|---|---|---|---|
anchor | str | Yes | Text anchor pattern like {TagName} |
placement | str | Yes | "replace" | "before" | "after" | "above" | "below" |
size | Dict | Yes | { "width": int, "height": int } |
offset | Dict | No | { "x": int, "y": int } |
caseSensitive | bool | No | Case sensitive search (default: False) |
useRegex | bool | No | Use regex for anchor/searchText (default: False) |
field: Dict[str, Any] = {
"type": "signature",
"page": 1,
"x": 100,
"y": 500,
"width": 200,
"height": 50,
"recipientEmail": "john@example.com"
}
Request Parameters
Request configuration for create_signature_review_link and send_signature methods:
| Parameter | Type | Required | Description |
|---|---|---|---|
recipients | List[Dict] | Yes | Recipients who will sign |
fields | List[Dict] | Yes | Signature fields configuration |
file | bytes | Conditional | PDF file content as bytes |
file_name | str | No | Original filename (used with file bytes) |
file_link | str | Conditional | URL to document file |
deliverable_id | str | Conditional | TurboDocx deliverable ID |
template_id | str | Conditional | TurboDocx template ID |
document_name | str | No | Document name |
document_description | str | No | Document description |
sender_name | str | No | Sender name (overrides the configured value) |
sender_email | str | No** | Sender / reply-to email (overrides the configured value) |
cc_emails | List[str] | No | Array of CC email addresses |
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 |
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 |
Each duration {"value", "unit"} uses "days" or "hours"; value is a whole number from 1
up to 999 days / 23976 hours. Reminder and expiration are independent and both off by
default. See Schedule reminders and expiration.
Exactly one file source is required: file, file_link, deliverable_id, or template_id.
** sender_email is optional per call but required at the SDK level for TurboSign: it must be supplied via configure(), the TURBODOCX_SENDER_EMAIL environment variable, or this per-call parameter, otherwise the SDK raises a ValidationError.
Additional Documentation
For detailed information about advanced configuration and API concepts, see:
Core API References
- Request Body Reference - Complete request body parameters, file sources, and multipart/form-data structure
- Recipients Reference - Recipient properties, signing order, metadata, and configuration options
- Field Types Reference - All available field types (signature, date, text, checkbox, etc.) with properties and behaviors
- Field Positioning Methods - Template-based vs coordinate-based positioning, anchor configuration, and best practices