TurboSign Java 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 Java applications. Build document generation and digital signature workflows with the Builder pattern, comprehensive error handling, and type-safe APIs. Available on Maven Central as com.turbodocx:turbodocx-sdk.
Installation
- Maven
- Gradle (Kotlin)
- Gradle (Groovy)
<dependency>
<groupId>com.turbodocx</groupId>
<artifactId>turbodocx-sdk</artifactId>
<version>0.5.0</version>
</dependency>
implementation("com.turbodocx:turbodocx-sdk:0.5.0")
implementation 'com.turbodocx:turbodocx-sdk:0.5.0'
Requirements
- Java 11+
- OkHttp 4.x (included)
- Gson 2.x (included)
Configuration
import com.turbodocx.TurboDocxClient;
public class Main {
public static void main(String[] args) {
// Create client with Builder pattern
TurboDocxClient client = new TurboDocxClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL"))
.build();
// Or with custom base URL
TurboDocxClient client = new TurboDocxClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL"))
.baseUrl("https://api.turbodocx.com")
.build();
}
}
Builder Options
| Method | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey(String) | String | Yes* | - | Organization API key |
accessToken(String) | String | Yes* | - | Bearer access token (alternative to apiKey) |
orgId(String) | String | Yes | - | Organization ID |
senderEmail(String) | String | Yes | - | Reply-to address for signature request emails |
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 |
*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);
}
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
export TURBODOCX_API_KEY=your_api_key_here
export TURBODOCX_ORG_ID=your_org_id_here
export TURBODOCX_SENDER_EMAIL=sender@yourcompany.com
Three parameters are required for TurboSign operations: apiKey (or accessToken), orgId, and senderEmail. The senderEmail is used as the reply-to address for signature request emails. To get your credentials, follow the Get Your Credentials steps from the SDKs main page.
Quick Start
Send a Document for Signature
import com.turbodocx.TurboDocxClient;
import com.turbodocx.models.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.Arrays;
public class Main {
public static void main(String[] args) throws Exception {
TurboDocxClient client = new TurboDocxClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL"))
.build();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.fileLink("https://www.turbodocx.com/examples/turbodocx.pdf")
.documentName("Service Agreement")
.senderName("Acme Corp")
.senderEmail("contracts@acme.com")
.recipients(Arrays.asList(
new Recipient("Alice Smith", "alice@example.com", 1),
new Recipient("Bob Johnson", "bob@example.com", 2)
))
.fields(Arrays.asList(
// Alice's signature
new Field("signature", 1, 100, 650, 200, 50, "alice@example.com"),
// defaultValue pins a fixed date in MM/DD/YYYY; omit to auto-fill the signing date
new Field("date", 1, 320, 650, 100, 30, "alice@example.com",
"12/31/2026", null, null, null, null, null, null),
// Bob's signature
new Field("signature", 1, 100, 720, 200, 50, "bob@example.com"),
new Field("date", 1, 320, 720, 100, 30, "bob@example.com")
))
.build()
);
System.out.println("Result: " + gson.toJson(result));
}
}
Using Template-Based Fields
// Template-based field using anchor text
Field.TemplateAnchor templateAnchor = new Field.TemplateAnchor(
"{SIGNATURE_ALICE}", // anchor text to find
null, // searchText (alternative to anchor)
"replace", // placement: replace/before/after/above/below
new Field.Size(200, 50), // size
null, // offset
false, // caseSensitive
false // useRegex
);
// Field with template anchor (no page/x/y coordinates needed)
Field templateField = new Field(
"signature", // type
null, // page (null for template-based)
null, // x (null for template-based)
null, // y (null for template-based)
null, // width (null, using template size)
null, // height (null, using template size)
"alice@example.com", // recipientEmail
null, // defaultValue
null, // isMultiline
null, // isReadonly
null, // required
null, // backgroundColor
templateAnchor // template anchor config
);
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.fileLink("https://www.turbodocx.com/examples/turbodocx.pdf")
.recipients(Arrays.asList(
new Recipient("Alice Smith", "alice@example.com", 1)
))
.fields(Arrays.asList(templateField))
.build()
);
Important: The document file must contain the anchor text (e.g., {SIGNATURE_ALICE}, {DATE_ALICE}) that you reference in your fields. If the anchors don't exist in the document, the API will return an error.
File Input Methods
The SDK supports multiple ways to provide your document:
1. File Upload (byte[])
Upload a document directly from file bytes:
import java.nio.file.Files;
import java.nio.file.Paths;
byte[] pdfBytes = Files.readAllBytes(Paths.get("/path/to/document.pdf"));
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.file(pdfBytes)
.recipients(Arrays.asList(
new Recipient("John Doe", "john@example.com", 1)
))
.fields(Arrays.asList(
new Field("signature", 1, 100, 500, 200, 50, "john@example.com")
))
.build()
);
2. File URL
Provide a publicly accessible URL to your document:
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.fileLink("https://www.turbodocx.com/examples/turbodocx.pdf")
.recipients(Arrays.asList(
new Recipient("John Doe", "john@example.com", 1)
))
.fields(Arrays.asList(
new Field("signature", 1, 100, 500, 200, 50, "john@example.com")
))
.build()
);
Use fileLink when your documents are already hosted on cloud storage (S3, Google Cloud Storage, etc.). This is more efficient than downloading and re-uploading files.
3. TurboDocx Deliverable ID
Use a document generated by TurboDocx document generation:
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.deliverableId("deliverable-uuid-from-turbodocx")
.recipients(Arrays.asList(
new Recipient("John Doe", "john@example.com", 1)
))
.fields(Arrays.asList(
new Field("signature", 1, 100, 500, 200, 50, "john@example.com")
))
.build()
);
deliverableId references documents generated using TurboDocx's document generation API. This creates a seamless workflow: generate → sign.
4. TurboDocx Template ID
Use a pre-configured TurboDocx template:
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.templateId("template-uuid-from-turbodocx")
.recipients(Arrays.asList(
new Recipient("John Doe", "john@example.com", 1)
))
.fields(Arrays.asList(
new Field("signature", 1, 100, 500, 200, 50, "john@example.com")
))
.build()
);
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
The snippets below assume a configured client and an available Gson instance, e.g. Gson gson = new GsonBuilder().setPrettyPrinting().create(); (as shown in the Quick Start).
Configure
Create a new TurboDocx client using the Builder pattern.
TurboDocxClient client = new TurboDocxClient.Builder()
.apiKey("your-api-key") // Required
.orgId("your-org-id") // Required
.senderEmail("sender@yourcompany.com") // Required for TurboSign
.baseUrl("https://api.turbodocx.com") // Optional
.build();
Prepare for review
Upload a document for preview without sending emails.
CreateSignatureReviewLinkResponse result = client.turboSign().createSignatureReviewLink(
new CreateSignatureReviewLinkRequest.Builder()
.fileLink("https://www.turbodocx.com/examples/turbodocx.pdf")
.documentName("Contract Draft")
.recipients(Arrays.asList(
new Recipient("John Doe", "john@example.com", 1)
))
.fields(Arrays.asList(
new Field("signature", 1, 100, 500, 200, 50, "john@example.com")
))
.build()
);
System.out.println("Result: " + gson.toJson(result));
Prepare for signing
Upload a document and immediately send signature requests.
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
.fileLink("https://www.turbodocx.com/examples/turbodocx.pdf")
.documentName("Service Agreement")
.senderName("Your Company")
.senderEmail("sender@company.com")
.recipients(Arrays.asList(
new Recipient("Recipient Name", "recipient@example.com", 1)
))
.fields(Arrays.asList(
new Field("signature", 1, 100, 500, 200, 50, "recipient@example.com")
))
.build()
);
System.out.println("Result: " + gson.toJson(result));
Schedule reminders and expiration
sendSignature accepts an optional SignatureSchedule that turns on automatic reminder emails and/or a signing deadline. Both features are off by default, so omitting the schedule preserves the original send behavior. The resolved deadline is frozen onto the document at send time and is then readable via getStatus().getExpiresAt().
SignatureSchedule schedule = SignatureSchedule.builder()
.remindersEnabled(true)
.reminderDelay(new SignatureSchedule.Duration(3, "days")) // time to the first reminder
.reminderInterval(new SignatureSchedule.Duration(3, "days")) // gap between later reminders
.maxReminders(5) // -1..50: -1 unlimited, 0 none (default 5)
.expirationEnabled(true)
.expireAfter(new SignatureSchedule.Duration(14, "days")) // how long the document stays signable
.expirationWarning(new SignatureSchedule.Duration(2, "days")) // 0 = never warn
.expirationWarningInterval(new SignatureSchedule.Duration(1, "days"))
.build();
SendSignatureResponse result = client.turboSign().sendSignature(
new SendSignatureRequest.Builder()
// ...file, recipients, fields, etc.
.schedule(schedule)
.build()
);
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.
DocumentStatusResponse status = client.turboSign().getStatus("document-uuid");
System.out.println("Status: " + status.getStatus()); // "under_review", "completed", "voided", "expired"
System.out.println("Expires: " + status.getExpiresAt()); // ISO 8601, or null when expiration is off
System.out.println("Result: " + gson.toJson(status));
Get recipients
See who the document went to, who has signed, who you are still waiting on, and who sent it.
DocumentRecipientsResponse progress = client.turboSign().getRecipients("document-uuid");
System.out.println(progress.getSummary().getCompleted() + "/"
+ progress.getSummary().getTotal() + " signed, waiting on "
+ progress.getSummary().getWaitingOn());
for (DocumentRecipientsResponse.RecipientSignatureStatus r : progress.getRecipients()) {
System.out.println(r.getName() + " <" + r.getEmail() + ">: " + r.getEffectiveStatus()
+ " (emailed " + r.getDelivery().getTotalSent() + "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.
byte[] pdfData = client.turboSign().download("document-uuid");
// Save to file
Files.write(Paths.get("signed-contract.pdf"), pdfData);
Get audit trail
Retrieve the audit trail for a document.
AuditTrailResponse auditTrail = client.turboSign().getAuditTrail("document-uuid");
System.out.println("Result: " + gson.toJson(auditTrail));
Void
Cancel/void a signature request.
VoidDocumentResponse result = client.turboSign().voidDocument("document-uuid", "Contract terms changed");
Resend
Resend signature request emails.
// Resend to specific recipients
ResendEmailResponse result = client.turboSign().resendEmail(
"document-uuid",
Arrays.asList("recipient-uuid-1", "recipient-uuid-2")
);
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.
// Remind everyone whose turn it is
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());
}
// Or limit to specific recipients
client.turboSign().sendReminder("document-uuid", Arrays.asList("recipient-uuid-1"));
Error Handling
The SDK provides typed exceptions for different error scenarios:
Error Types
| Error Type | Status Code | Description |
|---|---|---|
TurboDocxException | varies | Base exception for all API errors |
TurboDocxException.AuthenticationException | 401 | Invalid or missing API credentials |
TurboDocxException.ValidationException | 400 | Invalid request parameters |
TurboDocxException.AuthorizationException | 403 | Authenticated but lacks permissions for the route |
TurboDocxException.NotFoundException | 404 | Document or resource not found |
TurboDocxException.ConflictException | 409 | Request conflicts with current state of resource (e.g., webhook name already exists) |
TurboDocxException.RateLimitException | 429 | Too many requests |
TurboDocxException.NetworkException | - | Network connectivity issues |
Error Properties
| Property | Type | Description |
|---|---|---|
getMessage() | String | Human-readable error message |
getStatusCode() | int | HTTP status code |
getCode() | String | Error code (if available) |
Example
import com.turbodocx.TurboDocxException;
try {
SendSignatureResponse result = client.turboSign().sendSignature(request);
} catch (TurboDocxException.AuthenticationException e) {
System.err.println("Authentication failed: " + e.getMessage());
// Check your API key and org ID
} catch (TurboDocxException.ValidationException e) {
System.err.println("Validation error: " + e.getMessage());
// Check request parameters
} catch (TurboDocxException.NotFoundException e) {
System.err.println("Not found: " + e.getMessage());
// Document or recipient doesn't exist
} catch (TurboDocxException.RateLimitException e) {
System.err.println("Rate limited: " + e.getMessage());
// Wait and retry
} catch (TurboDocxException.NetworkException e) {
System.err.println("Network error: " + e.getMessage());
// Check connectivity
} catch (TurboDocxException e) {
// Base exception for other API errors
System.err.println("API error [" + e.getStatusCode() + "]: " + e.getMessage());
}
Types
Signature Field Types
The type field accepts the following string values:
| Type | Description |
|---|---|
"signature" | Signature field |
"initial" | Initials field |
"text" | Text input field |
"date" | Date field |
"checkbox" | Checkbox field |
"full_name" | Full name field |
"first_name" | First name field |
"last_name" | Last name field |
"email" | Email field |
"title" | Title field |
"company" | Company field |
Recipient
| Property | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Recipient's full name |
email | String | Yes | Recipient's email address |
signingOrder | int | Yes | Order in which recipient should sign (1, 2, 3...) |
Field
The coordinate-based constructor takes positional arguments in this order: new Field(type, page, x, y, width, height, recipientEmail). For template-based fields, use the extended constructor shown in Using Template-Based Fields.
| Property | Type | Required | Description |
|---|---|---|---|
type | String | Yes | Field type (see table above) |
recipientEmail | String | Yes | Email of the recipient who fills this field |
page | Integer | No* | Page number (1-indexed) |
x | Integer | No* | X coordinate in pixels |
y | Integer | No* | Y coordinate in pixels |
width | Integer | No* | Field width in pixels |
height | Integer | No* | Field height in pixels |
defaultValue | String | No | Pre-filled value (checkbox: "true"/"false"; date: a fixed MM/DD/YYYY, omit to auto-fill the signing date) |
isMultiline | Boolean | No | Enable multiline for text fields |
isReadonly | Boolean | No | Make field read-only |
required | Boolean | No | Make field required |
backgroundColor | String | No | Background color |
template | TemplateAnchor | No | Template anchor configuration |
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.
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.
Template Configuration
When using template instead of coordinates:
| Property | Type | Required | Description |
| --------------- | --------- | -------- | ------------------------------------------------------------------------------------- | --- |
| anchor | String | Yes | Text to find in document (e.g., "{SIGNATURE}") | |
| placement | String | Yes | Position relative to anchor: "replace", "before", "after", "above", "below" |
| size | Size | Yes | Size with width and height |
| offset | Offset | No | Offset with x and y |
| caseSensitive | Boolean | No | Case-sensitive anchor search |
| useRegex | Boolean | No | Use regex for anchor search |
Request Parameters
Both CreateSignatureReviewLinkRequest and SendSignatureRequest accept:
| Property | Type | Required | Description |
|---|---|---|---|
file | byte[] | Conditional | File content as bytes |
fileLink | String | Conditional | URL to document |
deliverableId | String | Conditional | TurboDocx deliverable ID |
templateId | String | Conditional | TurboDocx template ID |
recipients | List<Recipient> | Yes | List of recipients |
fields | List<Field> | Yes | List of fields |
documentName | String | No | Document display name |
documentDescription | String | No | Document description |
senderName | String | No | Sender's name |
senderEmail | String | No | Sender's email |
ccEmails | List<String> | No | CC email addresses |
schedule | SignatureSchedule | No | Reminder + expiration schedule (see Schedule reminders and expiration) |
Exactly one file source is required: file, fileLink, deliverableId, or templateId.
Additional Documentation
For detailed information about advanced configuration and API concepts, see:
Core API References
- Request Body Reference - 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