Skip to main content

TurboSign SDK

Agent Skill

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.

bash — turbodocx
$npx skills add TurboDocx/quickstart
# then, inside your agent:›/turbodocx-sdk turbosign

The official TurboDocx SDK for JavaScript and TypeScript applications. Build document generation and digital signature workflows with full TypeScript support, async/await patterns, and comprehensive error handling. Available on npm as @turbodocx/sdk.

Installation​

npm install @turbodocx/sdk

Requirements​

  • Node.js 18+ or modern browser
  • TypeScript 4.7+ (optional, for type checking)

Configuration​

const { TurboSign } = require("@turbodocx/sdk");

// Configure globally (recommended for server-side)
TurboSign.configure({
apiKey: process.env.TURBODOCX_API_KEY, // Required : Your TurboDocx API key
orgId: process.env.TURBODOCX_ORG_ID, // Required: Your organization ID
senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // Required: reply-to address for signature request emails
senderName: "Your Company Name", // Optional but recommended: appears as the sender name
// Optional: override base URL for testing
// baseUrl: 'https://api.turbodocx.com'
});
Authentication

Authenticate using apiKey. API keys are recommended for server-side applications.

Builder Options​

MethodTypeRequiredDefaultDescription
apiKey(String)StringYes*-Organization API key
accessToken(String)StringYes*-Bearer access token (alternative to apiKey)
orgId(String)StringYes-Organization ID
senderEmail(String)StringYes-Reply-to address for signature request emails
senderName(String)StringNo-Display name used on signature request emails
baseUrl(String)StringNohttps://api.turbodocx.comAPI base URL
connectTimeoutSeconds(int)intNo60Connection timeout
readTimeoutSeconds(int)intNo120Read timeout, raise it for large document uploads
writeTimeoutSeconds(int)intNo60Write timeout, raise it for large document uploads

*Provide either apiKey or accessToken.

// Tune the timeouts for large documents
TurboDocxClient client = new TurboDocxClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL"))
.connectTimeoutSeconds(30)
.readTimeoutSeconds(300)
.writeTimeoutSeconds(300)
.build();

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:

try (TurboDocxClient client = new TurboDocxClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL"))
.build()) {

SendSignatureResponse result = client.turboSign().sendSignature(request);
}
Reuse a single client

Creating a client per request leaks OkHttp threads until they are closed. Prefer one long-lived client for the life of your application and call client.close() during shutdown.

Environment Variables​

# .env
TURBODOCX_API_KEY=your_api_key_here
TURBODOCX_ORG_ID=your_org_id_here

Quick Start​

Send a Document for Signature​

const { TurboSign } = require("@turbodocx/sdk");

TurboSign.configure({
apiKey: process.env.TURBODOCX_API_KEY,
orgId: process.env.TURBODOCX_ORG_ID,
senderEmail: process.env.TURBODOCX_SENDER_EMAIL,
});

(async () => {
// Send document with coordinate-based fields
const result = await TurboSign.sendSignature({
fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
documentName: "Service Agreement",
senderName: "Acme Corp",
senderEmail: "contracts@acme.com",
recipients: [
{ name: "Alice Smith", email: "alice@example.com", signingOrder: 1 },
{ name: "Bob Johnson", email: "bob@example.com", signingOrder: 2 },
],
fields: [
// Alice's signature
{
type: "signature",
page: 1,
x: 100,
y: 650,
width: 200,
height: 50,
recipientEmail: "alice@example.com",
},
{
type: "date",
page: 1,
x: 320,
y: 650,
width: 100,
height: 30,
recipientEmail: "alice@example.com",
// Pins a fixed date in MM/DD/YYYY; omit to auto-fill the signing date
defaultValue: "12/31/2026",
},
// Bob's signature
{
type: "signature",
page: 1,
x: 100,
y: 720,
width: 200,
height: 50,
recipientEmail: "bob@example.com",
},
{
type: "date",
page: 1,
x: 320,
y: 720,
width: 100,
height: 30,
recipientEmail: "bob@example.com",
},
],
});

console.log(JSON.stringify(result, null, 2));
})();

Using Template-Based Fields​

// Use text anchors instead of coordinates
const result = await TurboSign.sendSignature({
fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
recipients: [
{ name: "Alice Smith", email: "alice@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
recipientEmail: "alice@example.com",
template: {
anchor: "{SIGNATURE_ALICE}",
placement: "replace",
size: { width: 200, height: 50 },
},
},
{
type: "date",
recipientEmail: "alice@example.com",
template: {
anchor: "{DATE_ALICE}",
placement: "replace",
size: { width: 100, height: 30 },
},
},
],
});

console.log(JSON.stringify(result, null, 2));
Template Anchors Required

Important: The document file must contain the anchor text (e.g., {SIGNATURE_ALICE}, {DATE_ALICE}) that you reference in your fields. If the anchors don't exist in the document, the API will return an error.

Alternative: Use a TurboDocx template with pre-configured anchors:

const result = await TurboSign.sendSignature({
templateId: "template-uuid-from-turbodocx", // Template already contains anchors
recipients: [
{ name: "Alice Smith", email: "alice@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
recipientEmail: "alice@example.com",
template: {
anchor: "{SIGNATURE_ALICE}",
placement: "replace",
size: { width: 200, height: 50 },
},
},
],
});

File Input Methods​

TurboSign supports four different ways to provide document files:

1. File Upload ([]byte) / 1. File Upload (byte[])​

Upload a document directly from file bytes:

pdfBytes, err := os.ReadFile("/path/to/document.pdf")
if err != nil {
log.Fatal(err)
}

result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{
File: pdfBytes,
Recipients: []turbodocx.Recipient{
{Name: "John Doe", Email: "john@example.com", SigningOrder: 1},
},
Fields: []turbodocx.Field{
{Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "john@example.com"},
},
})

1. File Upload (Direct)​

<?php

use TurboDocx\TurboSign;
use TurboDocx\Config\HttpClientConfig;
use TurboDocx\Types\Recipient;
use TurboDocx\Types\Field;
use TurboDocx\Types\SignatureFieldType;
use TurboDocx\Types\Requests\SendSignatureRequest;

TurboSign::configure(HttpClientConfig::fromEnvironment());

$pdfContent = file_get_contents('./contract.pdf');

$result = TurboSign::sendSignature(
new SendSignatureRequest(
file: $pdfContent,
fileName: 'contract.pdf', // Optional
recipients: [
new Recipient('John Doe', 'john@example.com', 1)
],
fields: [
new Field(
type: SignatureFieldType::SIGNATURE,
recipientEmail: 'john@example.com',
page: 1,
x: 100,
y: 500,
width: 200,
height: 50
)
]
)
);

2. File URL​

<?php

use TurboDocx\TurboSign;
use TurboDocx\Types\Recipient;
use TurboDocx\Types\Field;
use TurboDocx\Types\SignatureFieldType;
use TurboDocx\Types\Requests\SendSignatureRequest;

$result = TurboSign::sendSignature(
new SendSignatureRequest(
fileLink: 'https://www.turbodocx.com/examples/turbodocx.pdf',
recipients: [
new Recipient('John Doe', 'john@example.com', 1)
],
fields: [
new Field(
type: SignatureFieldType::SIGNATURE,
recipientEmail: 'john@example.com',
page: 1,
x: 100,
y: 500,
width: 200,
height: 50
)
]
)
);
When to use fileLink

Use fileLink when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files.

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",
},
],
)

1. File Upload (Path, Buffer, or Browser File)​

const { readFileSync } = require("fs");
const { TurboSign } = require("@turbodocx/sdk");

const fileBuffer = readFileSync("./contract.pdf");

(async () => {
const result = await TurboSign.sendSignature({
file: fileBuffer,
recipients: [
{ name: "John Doe", email: "john@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
page: 1,
x: 100,
y: 650,
width: 200,
height: 50,
recipientEmail: "john@example.com",
},
],
});
})();
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).

When file is a Buffer, the filename defaults to document.pdf (extension detected from the content). Pass fileName to control it:

await TurboSign.sendSignature({
file: fileBuffer,
fileName: "acme-msa.pdf",
// ...
});
const result = await TurboSign.sendSignature({
fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
recipients: [
{ name: "John Doe", email: "john@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
page: 1,
x: 100,
y: 650,
width: 200,
height: 50,
recipientEmail: "john@example.com",
},
],
});
When to use fileLink

Use fileLink when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files.

3. TurboDocx Deliverable ID​

// Use a previously generated TurboDocx document
const result = await TurboSign.sendSignature({
deliverableId: "deliverable-uuid-from-turbodocx",
recipients: [
{ name: "John Doe", email: "john@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
page: 1,
x: 100,
y: 650,
width: 200,
height: 50,
recipientEmail: "john@example.com",
},
],
});
Integration with TurboDocx

deliverableId references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign.

4. TurboDocx Template ID​

// Use a pre-configured TurboSign template
const result = await TurboSign.sendSignature({
templateId: "template-uuid-from-turbodocx", // Template already contains anchors
recipients: [
{ name: "Alice Smith", email: "alice@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
recipientEmail: "alice@example.com",
template: {
anchor: "{SIGNATURE_ALICE}",
placement: "replace",
size: { width: 200, height: 50 },
},
},
],
});
Integration with TurboDocx

templateId references pre-configured TurboSign templates created in the TurboDocx dashboard. These templates come with built-in anchors and field positioning, making it easy to reuse signature workflows across multiple documents.


API Reference​

Async context

The snippets below use await, so they must run inside an async function. For a standalone script, wrap the call and run it with asyncio.run(...), and add import json if the snippet calls json.dumps:

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({
apiKey: string; // Required : Your TurboDocx API key
orgId: string; // Required: Your organization ID
senderEmail: string; // Required: reply-to address for signature request emails
senderName?: string; // Optional: sender name in emails (defaults to your API key's name)
baseUrl?: string; // Optional: API base URL (default: 'https://api.turbodocx.com')
});

Example:

const { TurboSign } = require("@turbodocx/sdk");

TurboSign.configure({
apiKey: process.env.TURBODOCX_API_KEY,
orgId: process.env.TURBODOCX_ORG_ID,
senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // Required for TurboSign
// Optional: override for testing
// baseUrl: 'https://api.turbodocx.com'
});
API Credentials Required

Both apiKey and orgId parameters are required for all API requests. To get your credentials, follow the Get Your Credentials steps from the SDKs main page.

Prepare for review​

Upload a document for preview without sending signature request emails.

const { documentId, previewUrl } = await TurboSign.createSignatureReviewLink({
fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
documentName: "Contract Draft",
recipients: [
{ name: "John Doe", email: "john@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
page: 1,
x: 100,
y: 500,
width: 200,
height: 50,
recipientEmail: "john@example.com",
},
],
});

Prepare for signing​

Upload a document and immediately send signature requests to all recipients.

const { documentId } = await TurboSign.sendSignature({
fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
documentName: "Service Agreement",
senderName: "Your Company",
senderEmail: "sender@company.com",
recipients: [
{ name: "Recipient Name", email: "recipient@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
page: 1,
x: 100,
y: 500,
width: 200,
height: 50,
recipientEmail: "recipient@example.com",
},
],
});

Reminders and expiration​

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: changing your org defaults later never alters a document already out for signature.

use TurboDocx\Types\Requests\SendSignatureRequest;

$result = TurboSign::sendSignature(
new SendSignatureRequest(
// ...recipients, fields, fileLink, documentName
remindersEnabled: true,
reminderDelay: ['value' => 3, 'unit' => 'days'], // time to the FIRST reminder
reminderInterval: ['value' => 3, 'unit' => 'days'], // gap between later reminders
maxReminders: 5, // -1 unlimited, 0 none, max 50 (default 5)
expirationEnabled: true,
expireAfter: ['value' => 30, 'unit' => 'days'], // how long the document stays signable
expirationWarning: ['value' => 3, 'unit' => 'days'], // 0 = never warn
expirationWarningInterval: ['value' => 1, 'unit' => 'days']
)
);

Durations are ['value' => N, 'unit' => 'hours'|'days'] objects. value is a whole number from 1 up to a maximum of 999 days (23976 hours); expirationWarning also accepts 0 to disable warnings. maxReminders accepts -1 (unlimited), 0 (none), or any value up to 50. The resulting deadline is readable afterwards via getStatus()->expiresAt.

Send a reminder​

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 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.

// Remind everyone whose turn it is to sign.
$result = TurboSign::sendReminder('document-uuid');

foreach ($result['results'] as $r) {
// 'sent', 'skipped_wrong_order', 'skipped_completed', ...
echo "{$r['recipientId']}: {$r['status']}\n";
}

// 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']);

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.

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.

const result = await TurboSign.sendSignature({
// ...fileLink, recipients, fields, etc.

// 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
expirationEnabled: true,
expireAfter: { value: 30, unit: "days" }, // how long the document stays signable
expirationWarning: { value: 3, unit: "days" }, // how far before expiry warnings start
expirationWarningInterval: { value: 1, unit: "days" },
});

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).

FieldTypeDefaultNotes
remindersEnabledbooleanfalseSend reminder emails at all
reminderDelayDuration3 daysTime to the first reminder, measured from that signer's invitation
reminderIntervalDuration3 daysGap between subsequent reminders
maxRemindersnumber5Cap per signer, range -1..50 (-1 unlimited, 0 none). Never caps expiry warnings
expirationEnabledbooleanfalseExpire the document at all
expireAfterDuration120 daysHow long the document stays signable, counted from sending
expirationWarningDuration3 daysHow far before expiry warnings start. 0 = never warn
expirationWarningIntervalDuration1 dayGap 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 for the full list.

Send reminder​

Send a standalone reminder to whoever's turn it is to sign. Unlike the scheduled reminders above, it ignores the configured cadence, works even when reminders are disabled or the per-signer cap is already spent, and does not consume that cap. Only signers at the current signing order are eligible. It maps to POST /turbosign/documents/{documentId}/send-reminder.

// Remind everyone whose turn it is: omit the recipient ids
const { results } = await TurboSign.sendReminder("document-uuid");

results.forEach((r) => {
// Anyone not emailed comes back as a skipped_* status, so you can tell who was reached
console.log(`${r.recipientId}: ${r.status}`); // e.g. "sent", "skipped_wrong_order"
});

// Or limit the reminder to specific recipients
await TurboSign.sendReminder("document-uuid", ["recipient-uuid-1"]);
Omit: don't send an empty array

To remind everyone eligible, omit recipientIds entirely. Passing an empty array ([]) is rejected with a 400.

Get status​

Retrieve the document-level status. For per-signer detail, use Get recipients.

const result = await TurboSign.getStatus("document-uuid");

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.

Get recipients​

See who the document went to, who has signed, who you are still waiting on, and who sent it.

const { document, recipients, summary } = await TurboSign.getRecipients("document-uuid");

console.log(`Sent by ${document.sentBy.name} on ${document.sentOn ?? "not sent yet"}`);
console.log(`${summary.completed}/${summary.total} signed, waiting on ${summary.waitingOn}`);

recipients.forEach((r) => {
console.log(`${r.name} <${r.email}>: ${r.effectiveStatus}`);
console.log(` emailed ${r.delivery.totalSent}x, last ${r.delivery.lastSentOn ?? "never"}`);
});
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 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 suggest

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.

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 a PDF Blob.

const { writeFileSync } = require("fs");

(async () => {
const result = await TurboSign.download("document-uuid");

// Node.js: Save to file
const buffer = Buffer.from(await result.arrayBuffer());
writeFileSync("signed-contract.pdf", buffer);
})();

Void​

Cancel/void a signature request.

await TurboSign.void("document-uuid", "Contract terms changed");

Resend​

Resend signature request emails to specific recipients.

// Resend to specific recipients
await TurboSign.resend("document-uuid", [
"recipient-uuid-1",
"recipient-uuid-2",
]);

Get audit trail​

Retrieve the complete audit trail for a document, including all events and actions.

const result = await TurboSign.getAuditTrail("document-uuid");

console.log(JSON.stringify(result, null, 2));

Field Types​

TurboSign supports 11 different field types using PHP enums:

use TurboDocx\Types\SignatureFieldType;

SignatureFieldType::SIGNATURE // Signature field
SignatureFieldType::INITIAL // Initial field
SignatureFieldType::DATE // Date stamp (auto-filled when signed)
SignatureFieldType::TEXT // Free text input
SignatureFieldType::FULL_NAME // Full name (auto-filled from recipient)
SignatureFieldType::FIRST_NAME // First name
SignatureFieldType::LAST_NAME // Last name
SignatureFieldType::EMAIL // Email address
SignatureFieldType::TITLE // Job title
SignatureFieldType::COMPANY // Company name
SignatureFieldType::CHECKBOX // Checkbox field

Field Positioning​

TurboSign supports two ways to position fields:

1. Coordinate-Based (Pixel Perfect)​

new Field(
type: SignatureFieldType::SIGNATURE,
recipientEmail: 'john@example.com',
page: 1, // Page number (1-indexed)
x: 100, // X coordinate
y: 500, // Y coordinate
width: 200, // Width in pixels
height: 50 // Height in pixels
)

2. Template Anchors (Dynamic)​

use TurboDocx\Types\TemplateConfig;
use TurboDocx\Types\FieldPlacement;

new Field(
type: SignatureFieldType::SIGNATURE,
recipientEmail: 'john@example.com',
template: new TemplateConfig(
anchor: '{signature1}', // Text to find in PDF
placement: FieldPlacement::REPLACE, // How to place the field
size: ['width' => 100, 'height' => 30]
)
)

Placement Options:

  • FieldPlacement::REPLACE - Replace the anchor text
  • FieldPlacement::BEFORE - Place before the anchor
  • FieldPlacement::AFTER - Place after the anchor
  • FieldPlacement::ABOVE - Place above the anchor
  • FieldPlacement::BELOW - Place below the anchor

Advanced Field Options​

// Checkbox (pre-checked, readonly)
new Field(
type: SignatureFieldType::CHECKBOX,
recipientEmail: 'john@example.com',
page: 1,
x: 100,
y: 600,
width: 20,
height: 20,
defaultValue: 'true', // Pre-checked
isReadonly: true // Cannot be unchecked
)

// Multiline text field
new Field(
type: SignatureFieldType::TEXT,
recipientEmail: 'john@example.com',
page: 1,
x: 100,
y: 200,
width: 400,
height: 100,
isMultiline: true, // Allow multiple lines
required: true, // Field is required
backgroundColor: '#f0f0f0' // Background color
)

// Readonly text (pre-filled, non-editable)
new Field(
type: SignatureFieldType::TEXT,
recipientEmail: 'john@example.com',
page: 1,
x: 100,
y: 300,
width: 300,
height: 30,
defaultValue: 'This text is pre-filled',
isReadonly: true
)

Conditional (IF/THEN) Fields​

The optional metadata builds IF/THEN relationships between fields. Put a fieldKey on a controlling checkbox, then point each dependent field's controllingFieldKey back at it.

use TurboDocx\Types\FieldMetadata;
use TurboDocx\Types\FieldConditional;
use TurboDocx\Types\ConditionalOperator;
use TurboDocx\Types\ConditionalAction;

// Controlling checkbox, carries a stable fieldKey
new Field(
type: SignatureFieldType::CHECKBOX,
recipientEmail: 'reviewer@company.com',
page: 1,
x: 100,
y: 400,
width: 20,
height: 20,
metadata: new FieldMetadata(fieldKey: 'request_changes')
);

// Dependent text field, hidden until the checkbox is checked
new Field(
type: SignatureFieldType::TEXT,
recipientEmail: 'reviewer@company.com',
page: 1,
x: 130,
y: 400,
width: 300,
height: 60,
metadata: new FieldMetadata(
conditional: new FieldConditional(
controllingFieldKey: 'request_changes', // = the checkbox's fieldKey
operator: ConditionalOperator::IS_CHECKED, // ::IS_CHECKED | ::IS_NOT_CHECKED
action: ConditionalAction::SHOW // ::SHOW | ::UNLOCK
)
)
);

Use action: 'unlock' to keep a field visible but read-only until the box is checked. 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.


Error Handling​

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 Types​

Error TypeStatus CodeDescription
TurboDocxErrorvariesBase error type for all API errors
AuthenticationError401Invalid or missing API key
AuthorizationError403Authenticated but lacks required permissions
ValidationError400Invalid request parameters
NotFoundError404Resource not found
ConflictError409Request conflicts with current resource state; most common on the webhook routes (creating or renaming to a name that already exists)
RateLimitError429Too many requests
NetworkError-Network connectivity issues

Error Classes​

Error ClassStatus CodeCodeDescription
TurboDocxErrorvariesvariesBase error class for all SDK errors
AuthenticationError401AUTHENTICATION_ERRORInvalid or missing API credentials
AuthorizationError403AUTHORIZATION_ERRORAPI key lacks required permissions
ValidationError400VALIDATION_ERRORInvalid request parameters
NotFoundError404NOT_FOUNDDocument or resource not found
ConflictError409CONFLICTResource conflict
RateLimitError429RATE_LIMIT_EXCEEDEDToo many requests
NetworkError-NETWORK_ERRORNetwork connectivity issues

Handling Errors​

const {
TurboSign,
TurboDocxError,
AuthenticationError,
ValidationError,
NotFoundError,
RateLimitError,
NetworkError,
} = require("@turbodocx/sdk");

(async () => {
try {
const result = await TurboSign.sendSignature({
fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
recipients: [
{ name: "John Doe", email: "john@example.com", signingOrder: 1 },
],
fields: [
{
type: "signature",
page: 1,
x: 100,
y: 650,
width: 200,
height: 50,
recipientEmail: "john@example.com",
},
],
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error("Authentication failed:", error.message);
// Check your API key and org ID
} else if (error instanceof ValidationError) {
console.error("Validation error:", error.message);
// Check request parameters
} else if (error instanceof NotFoundError) {
console.error("Resource not found:", error.message);
// Document or recipient doesn't exist
} else if (error instanceof RateLimitError) {
console.error("Rate limited:", error.message);
// Wait and retry
} else if (error instanceof NetworkError) {
console.error("Network error:", error.message);
// Check connectivity
} else if (error instanceof TurboDocxError) {
console.error("SDK error:", error.message, error.statusCode, error.code);
}
}
})();

Error Properties​

All errors include these readonly properties:

PropertyTypeDescription
messagestringHuman-readable error description
statusCodenumber | undefinedHTTP status code (if applicable)
codestring | undefinedMachine-readable error code

Example​

import (
"errors"

turbodocx "github.com/TurboDocx/SDK/packages/go-sdk"
)

result, err := client.TurboSign.SendSignature(ctx, request)
if err != nil {
// Check for specific error types
var authErr *turbodocx.AuthenticationError
var authzErr *turbodocx.AuthorizationError
var validationErr *turbodocx.ValidationError
var notFoundErr *turbodocx.NotFoundError
var conflictErr *turbodocx.ConflictError
var rateLimitErr *turbodocx.RateLimitError
var networkErr *turbodocx.NetworkError

switch {
case errors.As(err, &authErr):
log.Printf("Authentication failed: %s", authErr.Message)
case errors.As(err, &authzErr):
log.Printf("Authorization failed: %s", authzErr.Message)
case errors.As(err, &validationErr):
log.Printf("Validation error: %s", validationErr.Message)
case errors.As(err, &notFoundErr):
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):
log.Printf("Network error: %s", networkErr.Message)
default:
// Base TurboDocxError or unexpected error
var turboErr *turbodocx.TurboDocxError
if errors.As(err, &turboErr) {
log.Printf("API error [%d]: %s", turboErr.StatusCode, turboErr.Message)
} else {
log.Fatal(err)
}
}
}

TypeScript Types / Python Types / PHP Types / Types​

The SDK exports TypeScript types for full type safety. Import them directly from the package.

Signature Field Types​

The Type field accepts the following string values:

TypeDescription
"signature"Signature field
"initials"Initials field
"text"Text input field
"date"Date field
"checkbox"Checkbox field
"full_name"Full name field
"first_name"First name field
"last_name"Last name field
"email"Email field
"title"Title field
"company"Company field

Enums​

The SDK uses PHP 8.1+ enums for type safety:

// Field types
enum SignatureFieldType: string {
case SIGNATURE = 'signature';
case INITIAL = 'initial';
case DATE = 'date';
case TEXT = 'text';
case FULL_NAME = 'full_name';
case FIRST_NAME = 'first_name';
case LAST_NAME = 'last_name';
case EMAIL = 'email';
case TITLE = 'title';
case COMPANY = 'company';
case CHECKBOX = 'checkbox';
}

// Field placement
enum FieldPlacement: string {
case REPLACE = 'replace';
case BEFORE = 'before';
case AFTER = 'after';
case ABOVE = 'above';
case BELOW = 'below';
}

// Document status
enum DocumentStatus: string {
case DRAFT = 'draft';
case SETUP_COMPLETE = 'setup_complete';
case REVIEW_READY = 'review_ready';
case UNDER_REVIEW = 'under_review';
case COMPLETED = 'completed';
case VOIDED = 'voided';
}

// 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
enum ConditionalAction: string {
case SHOW = 'show'; // hidden until met
case UNLOCK = 'unlock'; // visible but read-only until met
}

Readonly Classes​

The SDK uses readonly classes with typed properties:

final class Recipient {
public function __construct(
public string $name,
public string $email,
public int $signingOrder
) {}
}

final class Field {
public function __construct(
public SignatureFieldType $type,
public string $recipientEmail,
public ?int $page = null,
public ?int $x = null,
public ?int $y = null,
public ?int $width = null,
public ?int $height = null,
public ?TemplateConfig $template = null,
public ?string $defaultValue = null, // checkbox: 'true'/'false'; date: a fixed MM/DD/YYYY (omit to auto-fill the signing date)
public bool $isMultiline = false,
public bool $isReadonly = false,
public bool $required = false,
public ?string $backgroundColor = null,
public ?FieldMetadata $metadata = null // Conditional (IF/THEN) metadata
) {}
}

// Conditional (IF/THEN) metadata
final class FieldMetadata {
public function __construct(
public ?string $fieldKey = null, // On a controlling checkbox
public ?FieldConditional $conditional = null // On a dependent field
) {}
}

final class FieldConditional {
public function __construct(
public string $controllingFieldKey, // = the checkbox's fieldKey (non-empty)
public ConditionalOperator $operator, // ConditionalOperator::IS_CHECKED | ::IS_NOT_CHECKED
public ConditionalAction $action // ConditionalAction::SHOW | ::UNLOCK
) {}
}

Request Objects​

final class SendSignatureRequest {
public function __construct(
public array $recipients, // Recipient[]
public array $fields, // Field[]
public ?string $file = null,
public ?string $fileName = null,
public ?string $fileLink = null,
public ?string $deliverableId = null,
public ?string $templateId = null,
public ?string $documentName = null,
public ?string $documentDescription = null,
public ?string $senderName = null,
public ?string $senderEmail = null,
public ?array $ccEmails = null
) {}
}
File Source (Conditional)

Exactly one file source is required: file, fileLink, deliverableId, or templateId.


Importing Types​

import type {
// Field types
SignatureFieldType,
Field,
Recipient,
// Request types
CreateSignatureReviewLinkRequest,
SendSignatureRequest,
} from "@turbodocx/sdk";

SignatureFieldType​

Union type for all available field types:

type SignatureFieldType =
| "signature"
| "initial"
| "date"
| "text"
| "full_name"
| "title"
| "company"
| "first_name"
| "last_name"
| "email"
| "checkbox";

Recipient​

Recipient configuration for signature requests:

PropertyTypeRequiredDescription
namestringYesRecipient's full name
emailstringYesRecipient's email address
signingOrdernumberYesSigning order (1-indexed)

Field​

Field configuration supporting both coordinate-based and template-based positioning:

PropertyTypeRequiredDescription
typeSignatureFieldTypeYesField type
recipientEmailstringYesWhich recipient fills this field
pagenumberNo*Page number (1-indexed)
xnumberNo*X coordinate in pixels
ynumberNo*Y coordinate in pixels
widthnumberNo*Field width in pixels
heightnumberNo*Field height in pixels
defaultValuestringNoDefault value (checkbox: "true"/"false"; date: a fixed MM/DD/YYYY, omit to auto-fill the signing date)
isMultilinebooleanNoEnable multiline text
isReadonlybooleanNoMake field read-only (pre-filled)
requiredbooleanNoWhether field is required
backgroundColorstringNoBackground color (hex, rgb, or named)
templateobjectNoTemplate anchor configuration
metadataobjectNoConditional (IF/THEN) metadata, see below

*Required when not using template anchors

Metadata Configuration (Conditional Fields):

The optional metadata object builds IF/THEN relationships between fields. Put a fieldKey on a controlling checkbox, then point each dependent field's conditional.controllingFieldKey back at it.

PropertyTypeRequiredDescription
fieldKeystringNoStable id on a controlling checkbox (type: "checkbox").
conditionalobjectNoRule on a dependent field (see below).
conditional.controllingFieldKeystringYesThe controlling checkbox's fieldKey. Must be non-empty.
conditional.operatorstringYes"is_checked" | "is_not_checked".
conditional.actionstringYes"show" (hidden until met) | "unlock" (locked until met).
// Checkbox reveals a text field when checked
const fields: Field[] = [
{
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:

PropertyTypeRequiredDescription
anchorstringNo†Text anchor pattern like {TagName}
searchTextstringNo†Alternative to anchor: search for any text in the document
placementstringNo"replace" | "before" | "after" | "above" | "below"
sizeobjectNo{ width: number; height: number }
offsetobjectNo{ x: number; y: number }
caseSensitivebooleanNoCase sensitive search (default: false)
useRegexbooleanNoUse regex for anchor/searchText (default: false)

†All template properties are optional in the type definition, but at least one of anchor or searchText must be provided for anchor-based positioning to work.

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.

PropertyTypeRequiredDescription
FieldKeystringNoStable id on a controlling checkbox (Type: "checkbox").
Conditional*FieldConditionalNoRule on a dependent field (see below).
Conditional.ControllingFieldKeystringYesThe controlling checkbox's FieldKey. Must be non-empty.
Conditional.OperatorstringYes"is_checked" or "is_not_checked".
Conditional.ActionstringYes"show" (hidden until met) or "unlock" (locked until met).
// Checkbox reveals a text field when checked
fields := []turbodocx.Field{
{
Type: "checkbox",
RecipientEmail: "reviewer@company.com",
Page: 1, X: 100, Y: 400, Width: 20, Height: 20,
Metadata: &turbodocx.FieldMetadata{
FieldKey: "request_changes",
},
},
{
Type: "text",
RecipientEmail: "reviewer@company.com",
Page: 1, X: 130, Y: 400, Width: 300, Height: 60,
Metadata: &turbodocx.FieldMetadata{
Conditional: &turbodocx.FieldConditional{
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​

When using Template instead of coordinates:

PropertyTypeRequiredDescription
AnchorstringYesText to find in document (e.g., "{SIGNATURE}")
PlacementstringYesPosition relative to anchor: "replace", "before", "after", "above", "below"
Size*SizeYesSize with Width and Height
Offset*PointNoOffset with X and Y
CaseSensitiveboolNoCase-sensitive anchor search
UseRegexboolNoUse regex for anchor search

Request Parameters​

Request configuration for create_signature_review_link and send_signature methods:

 

ParameterTypeRequiredDescription
recipientsList[Dict]YesRecipients who will sign
fieldsList[Dict]YesSignature fields configuration
filebytesConditionalPDF file content as bytes
file_namestrNoOriginal filename (used with file bytes)
file_linkstrConditionalURL to document file
deliverable_idstrConditionalTurboDocx deliverable ID
template_idstrConditionalTurboDocx template ID
document_namestrNoDocument name
document_descriptionstrNoDocument description
sender_namestrNoSender name (overrides the configured value)
sender_emailstrNo**Sender / reply-to email (overrides the configured value)
cc_emailsList[str]NoArray of CC email addresses
reminders_enabledboolNoSend reminder emails to signers who haven't signed. Off by default
reminder_delayDictNo{"value": N, "unit": "days"|"hours"}, time to the FIRST reminder
reminder_intervalDictNo{"value": N, "unit": ...}, gap between later reminders
max_remindersintNoCap per signer. -1 unlimited, 0 none, max 50. Default 5
expiration_enabledboolNoClose the signing window after expire_after. Off by default
expire_afterDictNo{"value": N, "unit": ...}, how long the document stays signable
expiration_warningDictNo{"value": N, "unit": ...}, how far before expiry warnings start. 0 = never warn
expiration_warning_intervalDictNo{"value": N, "unit": ...}, gap between warnings once they start
Duration bounds

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.

File Source (Conditional)

Exactly one file source is required: file, file_link, deliverable_id, or template_id.

** sender_email is optional per call but required at the SDK level for TurboSign: it must be supplied via configure(), the TURBODOCX_SENDER_EMAIL environment variable, or this per-call parameter, otherwise the SDK raises a ValidationError.


CreateSignatureReviewLinkRequest / SendSignatureRequest​

Request configuration for createSignatureReviewLink and sendSignature methods:

PropertyTypeRequiredDescription
filestring | File | BufferConditionalDocument as a local file path, Buffer, or browser File
fileNamestringNoOriginal filename, used when file is a Buffer (defaults to document.<ext>)
fileLinkstringConditionalURL to document file
deliverableIdstringConditionalTurboDocx deliverable ID
templateIdstringConditionalTurboDocx template ID
recipientsRecipient[]YesRecipients who will sign
fieldsField[]YesSignature fields configuration
documentNamestringNoDocument name
documentDescriptionstringNoDocument description
senderNamestringNoSender name, falls back to senderName in the SDK config, then your API key's name
senderEmailstringConditionalSender email, required on the request unless supplied via TurboSign.configure({ senderEmail }) or TURBODOCX_SENDER_EMAIL
ccEmailsstring[]NoArray of CC email addresses
remindersEnabledbooleanNoSend reminder emails to signers who haven't signed (default false)
reminderDelayDurationNo{ value, unit }, time to the first reminder
reminderIntervalDurationNo{ value, unit }, gap between later reminders
maxRemindersnumberNoCap per signer, range -1..50 (-1 unlimited, 0 none, default 5)
expirationEnabledbooleanNoClose the signing window after expireAfter (default false)
expireAfterDurationNo{ value, unit }, how long the document stays signable
expirationWarningDurationNo{ value, unit }, how far before expiry warnings start (0 = never warn)
expirationWarningIntervalDurationNo{ value, unit }, gap between warnings once they start
Durations

A Duration is { value: number, unit: "hours" | "days" }. value is a whole number from 1 to 999 days (23976 hours).

File Source (Conditional)

Exactly one file source is required: file, fileLink, deliverableId, or templateId.

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 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.


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

Resources​