Skip to main content

TurboQuote Java 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 TurboQuote integration end-to-end.

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

The official TurboDocx TurboQuote SDK for Java applications. Create and send sales quotes, manage line items, products, bundles, and price books — all from Java 11+. Distributed as com.turbodocx:turbodocx-sdk on Maven Central (same artifact as TurboSign and TurboWebhooks).


What is TurboQuote?

TurboQuote is TurboDocx's CPQ (Configure, Price, Quote) module. Build a product catalog, assemble quotes with line items, apply price book discounts, and send branded proposals to contacts — with optional TurboSign e-signature delivery via sendQuoteWithDeliverable. No senderEmail is required; TurboQuote handles its own email delivery.

Installation

<dependency>
<groupId>com.turbodocx</groupId>
<artifactId>turbodocx-sdk</artifactId>
<version>0.4.0</version>
</dependency>

Then import:

import com.turbodocx.TurboQuoteClient;
import com.turbodocx.TurboQuote;
import com.turbodocx.TurboDocxException;
import com.turbodocx.models.quote.*;

Requirements

  • Java 11 or higher
  • OkHttp 4.x (included transitively)
  • Gson 2.x (included transitively)
  • A TurboDocx API key (TDX- prefix) — no administrator role required

Configuration

import com.turbodocx.TurboQuoteClient;
import com.turbodocx.TurboQuote;

TurboQuoteClient client = new TurboQuoteClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.build();

TurboQuote tq = client.turboQuote();

TurboQuoteClient.Builder does not require senderEmail — TurboQuote sends quote emails itself via its own send flow, so the sender validation enforced by TurboDocxClient for TurboSign is skipped here. orgId is required. Construct TurboQuoteClient once and reuse tq across the lifetime of your application.

Environment Variables

TURBODOCX_API_KEY=your_api_key
TURBODOCX_ORG_ID=your_org_id
# optional — defaults to https://api.turbodocx.com
TURBODOCX_BASE_URL=https://api.turbodocx.com
API Credentials Required

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

Quick Start

1. Create a company, quote, and add line items

import com.turbodocx.TurboQuoteClient;
import com.turbodocx.TurboQuote;
import com.turbodocx.TurboDocxException;
import com.turbodocx.models.quote.*;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

public class QuoteLifecycle {
public static void main(String[] args) throws Exception {
TurboQuoteClient client = new TurboQuoteClient.Builder()
.apiKey(System.getenv("TURBODOCX_API_KEY"))
.orgId(System.getenv("TURBODOCX_ORG_ID"))
.build();

TurboQuote tq = client.turboQuote();

// Step 1: Create a company with an initial contact
CreateCompanyContactInput contact = new CreateCompanyContactInput();
contact.setName("Alice Buyer");
contact.setEmail("alice@example.com");

CreateCompanyRequest companyReq = new CreateCompanyRequest();
companyReq.setName("Acme Corp");
companyReq.setContacts(Arrays.asList(contact));

Company company = tq.createCompany(companyReq);
ContactListResponse contacts = tq.listCompanyContacts(company.getId());
String contactId = contacts.getResults().get(0).getId();

// Step 2: Create a quote
CreateQuoteRequest quoteReq = new CreateQuoteRequest();
quoteReq.setName("Q1 Software License");
quoteReq.setCompanyId(company.getId());
quoteReq.setContactId(contactId);
quoteReq.setTermDays(30);
quoteReq.setCurrency(Currency.USD);

Quote quote = tq.createQuote(quoteReq);
System.out.println("Quote: " + quote.getId() + " status=" + quote.getStatus());

// Step 3: Add a line item
AddLineItemRequest item = new AddLineItemRequest();
item.setProductName("Enterprise Software License");
item.setUnitPrice(1200.00);
item.setQuantity(3.0);
item.setBillingFrequency("annual");
item.setDiscountType(DiscountType.PERCENT);
item.setDiscountPercent(10.0);

List<LineItem> lineItems = tq.addLineItems(quote.getId(), item);
System.out.println("Added " + lineItems.size() + " line item(s)");

// Step 4: Send the quote
SendQuoteResponse sent = tq.sendQuote(quote.getId());
System.out.println("Sent. Status: " + sent.getQuote().getStatus());

// Step 5: Download the PDF
byte[] pdf = tq.downloadQuotePdf(quote.getId());
Files.write(Paths.get("quote.pdf"), pdf);
System.out.println("PDF saved (" + pdf.length + " bytes)");
}
}

2. Create and send in one call

createAndSend is a convenience method that creates the quote, adds line items and bundle items, and sends it atomically.

CreateAndSendRequest req = new CreateAndSendRequest();
req.setName("Partner Proposal");
req.setCompanyId(companyId);
req.setContactId(contactId);

AddLineItemRequest item = new AddLineItemRequest();
item.setProductName("Starter Plan");
item.setUnitPrice(499.00);
item.setQuantity(1.0);
req.setItems(Arrays.asList(item));

// req.setSend(...) to configure send options, or omit to use defaults

CreateAndSendResponse result = tq.createAndSend(req);
System.out.println("Quote created and sent: " + result.getQuote().getId());

3. Apply a price book, then send with a deliverable

// Apply a price book to recalculate line item prices
ApplyPriceBookResponse applied = tq.applyPriceBook(quoteId, priceBookId);
System.out.println("Updated: " + applied.getUpdatedCount()
+ ", Skipped: " + applied.getSkippedCount());

// Send with a TurboDocx deliverable attached
SendQuoteWithDeliverableRequest sendReq = new SendQuoteWithDeliverableRequest();
sendReq.setDeliverableId("your-deliverable-id");
sendReq.setMergePosition("end");

SendQuoteWithDeliverableResponse sendResp = tq.sendQuoteWithDeliverable(quoteId, sendReq);
System.out.println("Document ID: " + sendResp.getDocumentId());

Method Reference

All methods are instance methods on com.turbodocx.TurboQuote. Obtain the instance via client.turboQuote() from a constructed TurboQuoteClient. All methods throw IOException and TurboDocxException subclasses.


Quotes — CRUD

listQuotes

QuoteListResponse listQuotes()
QuoteListResponse listQuotes(ListQuotesOptions options)

List quotes with optional pagination and filters. Returns a paginated response including stats (totals, counts by status).

ListQuotesOptions opts = new ListQuotesOptions();
opts.setLimit(20);
opts.setOffset(0);

QuoteListResponse list = tq.listQuotes(opts);
System.out.println("Total: " + list.getTotalRecords());
list.getResults().forEach(q -> System.out.println(q.getId() + " " + q.getStatus()));

createQuote

Quote createQuote(CreateQuoteRequest request)

Create a new quote. Returns the created Quote.

CreateQuoteRequest req = new CreateQuoteRequest();
req.setName("Enterprise Proposal");
req.setCompanyId(companyId);
req.setContactId(contactId);
req.setCurrency(Currency.USD);
req.setTermDays(30);

Quote quote = tq.createQuote(req);

getQuote

Quote getQuote(String id)

Get a quote by ID. Returns the Quote with statusInfo merged in (expiry dates, status transitions).

Quote quote = tq.getQuote(quoteId);
System.out.println("Status: " + quote.getStatus());
// quote.getStatusInfo() — expiry/transition metadata

updateQuote

Quote updateQuote(String id, UpdateQuoteRequest request)

Update an existing quote. Only fields explicitly set on UpdateQuoteRequest are patched; unset fields are omitted from the request body. Fields explicitly set to null are cleared on the server (e.g., setValidUntil(null) clears the expiry date).

UpdateQuoteRequest req = new UpdateQuoteRequest();
req.setName("Revised Proposal — Q2");
req.setTermDays(60);

Quote updated = tq.updateQuote(quoteId, req);

deleteQuote

SuccessResponse deleteQuote(String id)

Delete a quote.

SuccessResponse resp = tq.deleteQuote(quoteId);
System.out.println(resp.getMessage());

duplicateQuote

Quote duplicateQuote(String id)

Duplicate a quote (creates a draft copy).

Quote copy = tq.duplicateQuote(quoteId);
System.out.println("New quote: " + copy.getId());

applyPriceBook

ApplyPriceBookResponse applyPriceBook(String quoteId, String priceBookId)

Apply a price book to a quote, recalculating line item prices. Returns {quote, message, updatedCount, skippedCount}.

ApplyPriceBookResponse resp = tq.applyPriceBook(quoteId, priceBookId);
System.out.println("Updated " + resp.getUpdatedCount() + " items.");

removePriceBook

Quote removePriceBook(String quoteId)

Remove the applied price book from a quote, restoring original line item pricing.

Quote quote = tq.removePriceBook(quoteId);

downloadQuotePdf

byte[] downloadQuotePdf(String id)

Download the quote as a PDF. Returns raw bytes.

byte[] pdf = tq.downloadQuotePdf(quoteId);
Files.write(Paths.get("quote.pdf"), pdf);

Quotes — Status Transitions

sendQuote

SendQuoteResponse sendQuote(String id)
SendQuoteResponse sendQuote(String id, SendQuoteRequest request)

Send a quote to the contact. Returns {quote, message}. Pass SendQuoteRequest to configure send options, or omit for defaults.

SendQuoteResponse resp = tq.sendQuote(quoteId);
System.out.println("Status: " + resp.getQuote().getStatus());

sendQuoteWithDeliverable

SendQuoteWithDeliverableResponse sendQuoteWithDeliverable(String id, SendQuoteWithDeliverableRequest request)

Send a quote with a TurboDocx deliverable attached. Returns {quote, message, documentId}.

SendQuoteWithDeliverableRequest req = new SendQuoteWithDeliverableRequest();
req.setDeliverableId("your-deliverable-id");
req.setMergePosition("end"); // "start" | "end"

SendQuoteWithDeliverableResponse resp = tq.sendQuoteWithDeliverable(quoteId, req);
System.out.println("Document ID: " + resp.getDocumentId());

declineQuote

Quote declineQuote(String id, DeclineQuoteRequest request)

Mark a sent quote as declined.

DeclineQuoteRequest req = new DeclineQuoteRequest();
req.setReason("Budget constraints");

Quote declined = tq.declineQuote(quoteId, req);

voidQuote

Quote voidQuote(String id, VoidQuoteRequest request)

Void a quote (cannot be undone).

VoidQuoteRequest req = new VoidQuoteRequest();
req.setReason("Replaced by new proposal");

Quote voided = tq.voidQuote(quoteId, req);

handleExpiredQuote

Quote handleExpiredQuote(String id, HandleExpiredQuoteRequest request)

Handle an expired sent quote — extend, re-send, or void it.

HandleExpiredQuoteRequest req = new HandleExpiredQuoteRequest();
req.setAction("extend"); // "extend" | "resend" | "void"
req.setNewValidUntil("2026-12-31");

Quote quote = tq.handleExpiredQuote(quoteId, req);

Line Items

listLineItems

LineItemListResponse listLineItems(String quoteId)
LineItemListResponse listLineItems(String quoteId, ListLineItemsOptions options)

List line items for a quote.

LineItemListResponse items = tq.listLineItems(quoteId);
items.getResults().forEach(i ->
System.out.println(i.getProductName() + " x" + i.getQuantity()));

addLineItems

List<LineItem> addLineItems(String quoteId, AddLineItemRequest item)
List<LineItem> addLineItems(String quoteId, List<AddLineItemRequest> items)

Add one or more product line items to a quote. A single AddLineItemRequest is automatically wrapped.

AddLineItemRequest item = new AddLineItemRequest();
item.setProductName("Enterprise License");
item.setUnitPrice(1200.00);
item.setQuantity(5.0);
item.setBillingFrequency("annual");

List<LineItem> added = tq.addLineItems(quoteId, item);

addBundleLineItems

List<LineItem> addBundleLineItems(String quoteId, AddBundleLineItemRequest item)
List<LineItem> addBundleLineItems(String quoteId, List<AddBundleLineItemRequest> items)

Add one or more bundle line items to a quote.

AddBundleLineItemRequest bundleItem = new AddBundleLineItemRequest();
bundleItem.setBundleId(bundleId);
bundleItem.setQuantity(2.0);

List<LineItem> added = tq.addBundleLineItems(quoteId, bundleItem);

updateLineItem

LineItem updateLineItem(String quoteId, String itemId, UpdateLineItemRequest request)

Update a line item on a quote. Only explicitly set fields are patched.

UpdateLineItemRequest req = new UpdateLineItemRequest();
req.setQuantity(10.0);
req.setDiscountPercent(15.0);

LineItem updated = tq.updateLineItem(quoteId, itemId, req);

removeLineItem

SuccessResponse removeLineItem(String quoteId, String itemId)

Remove a line item from a quote.

tq.removeLineItem(quoteId, itemId);

Products

MethodSignatureReturns
listProductslistProducts() / listProducts(ListProductsOptions)ProductListResponse
createProductcreateProduct(CreateProductRequest)Product
getProductgetProduct(String id)Product
updateProductupdateProduct(String id, UpdateProductRequest)Product
deleteProductdeleteProduct(String id)SuccessResponse
duplicateProductduplicateProduct(String id)Product
getProductPrimaryImagesgetProductPrimaryImages(List<String> productIds)Map<String, ProductImage>
// Create a product
CreateProductRequest req = new CreateProductRequest();
req.setName("Pro Platform");
req.setSku("PRO-001");
req.setListPrice(500.00);
req.setBillingFrequency("monthly");

Product product = tq.createProduct(req);

// Upload with images — pass byte[][] via setImages()
req.setImages(new byte[][] { imageBytes });
Product productWithImages = tq.createProduct(req);

// Get primary images for a set of products
Map<String, ProductImage> images = tq.getProductPrimaryImages(
Arrays.asList(product.getId(), anotherProductId));
// images.get(productId) — ProductImage or null
Multipart Upload

When CreateProductRequest.getImages() is non-empty, the SDK automatically switches to multipart form upload with magic-byte MIME type detection (PNG, JPEG, GIF, WebP supported).


Price Books

MethodSignatureReturns
listPriceBookslistPriceBooks() / listPriceBooks(ListPriceBooksOptions)PriceBookListResponse
createPriceBookcreatePriceBook(CreatePriceBookRequest)PriceBook
getPriceBookgetPriceBook(String id)PriceBook
updatePriceBookupdatePriceBook(String id, UpdatePriceBookRequest)PriceBook
deletePriceBookdeletePriceBook(String id)SuccessResponse
duplicatePriceBookduplicatePriceBook(String id)PriceBook
listPriceBookProductslistPriceBookProducts(String id) / listPriceBookProducts(String id, ListPriceBookProductsOptions)PriceBookProductListResponse
// Create a price book with per-product pricing overrides
PriceBookProductPricingInput pricing = new PriceBookProductPricingInput();
pricing.setProductId(productId);
pricing.setDiscountType(DiscountType.PERCENT);
pricing.setDiscountPercent(20.0);

CreatePriceBookRequest req = new CreatePriceBookRequest();
req.setName("Partner Discount"); // required
req.setPriceBookTypeId(typeId); // required — from a createType(categoryType=PRICEBOOK_TYPE)
req.setValidFrom("2025-01-01"); // required
req.setDiscountPercent(15.0); // required
req.setProductPricing(Arrays.asList(pricing));
req.setShowInQuoteBuilder(true);

PriceBook pb = tq.createPriceBook(req);

// List products in a price book
PriceBookProductListResponse pbProducts = tq.listPriceBookProducts(pb.getId());
System.out.println("Products: " + pbProducts.getTotalRecords());

Bundles

MethodSignatureReturns
listBundleslistBundles() / listBundles(ListBundlesOptions)BundleListResponse
createBundlecreateBundle(CreateBundleRequest)Bundle
getBundlegetBundle(String id)Bundle
updateBundleupdateBundle(String id, UpdateBundleRequest)Bundle
deleteBundledeleteBundle(String id)SuccessResponse
duplicateBundleduplicateBundle(String id)Bundle
CreateBundleRequest req = new CreateBundleRequest();
req.setName("Starter Bundle");
// configure items, pricing, etc.

Bundle bundle = tq.createBundle(req);

Companies

MethodSignatureReturns
listCompanieslistCompanies() / listCompanies(ListCompaniesOptions)CompanyListResponse
createCompanycreateCompany(CreateCompanyRequest)Company
getCompanygetCompany(String id)Company
updateCompanyupdateCompany(String id, UpdateCompanyRequest)Company
deleteCompanydeleteCompany(String id)SuccessResponse
listCompanyContactslistCompanyContacts(String companyId) / listCompanyContacts(String companyId, PaginationParams)ContactListResponse
// Create a company — contacts list is required (minimum one contact)
CreateCompanyContactInput contact = new CreateCompanyContactInput();
contact.setName("Alice Buyer");
contact.setEmail("alice@example.com");

CreateCompanyRequest req = new CreateCompanyRequest();
req.setName("Acme Corp");
req.setCity("New York");
req.setContacts(Arrays.asList(contact));

Company company = tq.createCompany(req);

// List contacts for that company
ContactListResponse contacts = tq.listCompanyContacts(company.getId());
String contactId = contacts.getResults().get(0).getId();

Contacts

MethodSignatureReturns
listContactslistContacts() / listContacts(ListContactsOptions)ContactListResponse
createContactcreateContact(CreateContactRequest)Contact
updateContactupdateContact(String id, UpdateContactRequest)Contact
deleteContactdeleteContact(String id)SuccessResponse
No getContact

There is no getContact(id) method — the backend has no GET /v1/contacts/:id endpoint. Use listContacts with a query filter, or listCompanyContacts to retrieve contacts for a known company.

CreateContactRequest req = new CreateContactRequest();
req.setName("Bob Partner");
req.setEmail("bob@partner.example.com");

Contact contact = tq.createContact(req);

Quote Templates

MethodSignatureReturns
listTemplateslistTemplates() / listTemplates(PaginationParams)QuoteTemplateListResponse
getTemplategetTemplate()QuoteTemplate
getTemplateByIdgetTemplateById(String id)QuoteTemplate
createTemplatecreateTemplate(CreateQuoteTemplateRequest)QuoteTemplate
updateTemplateupdateTemplate(String id, UpdateQuoteTemplateRequest)QuoteTemplate
deleteTemplatedeleteTemplate(String id)SuccessResponse
// Get the org's default template (singleton)
QuoteTemplate defaultTemplate = tq.getTemplate();

// Get a specific template by ID
QuoteTemplate template = tq.getTemplateById(templateId);

// List all templates
QuoteTemplateListResponse templates = tq.listTemplates();
getTemplate() vs getTemplateById(id)

getTemplate() hits GET /v1/quote-template (singular) and returns the organization's default template. getTemplateById(id) hits GET /v1/quote-templates/:id (plural) and returns a specific template by ID.


Types / Categories

MethodSignatureReturns
listTypeslistTypes() / listTypes(ListTypesOptions)QuoteTypeListResponse
createTypecreateType(CreateQuoteTypeRequest)QuoteType
updateTypeupdateType(String id, UpdateQuoteTypeRequest)QuoteType
deleteTypedeleteType(String id)SuccessResponse

Types are used for categorization — PRICEBOOK_TYPE and PRODUCT_CATEGORY are the two CategoryType values.

CreateQuoteTypeRequest req = new CreateQuoteTypeRequest();
req.setName("Partner Pricing");
req.setCategoryType(CategoryType.PRICEBOOK_TYPE);

QuoteType type = tq.createType(req);
No getType

There is no getType(id) method — the backend has no GET /v1/types/:id endpoint by design. Use listTypes to retrieve all types.


Convenience

createAndSend

CreateAndSendResponse createAndSend(CreateAndSendRequest request)

Create a quote, add line items and bundle items, and send it — all in one method call. Useful for programmatic quote generation pipelines.

AddLineItemRequest item = new AddLineItemRequest();
item.setProductName("Starter License");
item.setUnitPrice(999.00);
item.setQuantity(1.0);

CreateAndSendRequest req = new CreateAndSendRequest();
req.setName("Quick Proposal");
req.setCompanyId(companyId);
req.setContactId(contactId);
req.setItems(Arrays.asList(item));
// req.setSend(sendOptions) — optional

CreateAndSendResponse result = tq.createAndSend(req);
System.out.println("Quote: " + result.getQuote().getId());

Error Handling

import com.turbodocx.TurboDocxException;

try {
Quote quote = tq.createQuote(req);
} catch (TurboDocxException.ValidationException e) {
// 400 — invalid request body (missing required field, bad enum value, etc.)
System.err.println("Validation: " + e.getMessage());
} catch (TurboDocxException.AuthenticationException e) {
// 401 — bad or revoked API key
System.err.println("Auth: " + e.getMessage());
} catch (TurboDocxException.AuthorizationException e) {
// 403 — key lacks required role
System.err.println("Forbidden: " + e.getMessage());
} catch (TurboDocxException.NotFoundException e) {
// 404 — quote, product, company, etc. not found
System.err.println("Not found: " + e.getMessage());
} catch (TurboDocxException.RateLimitException e) {
// 429 — back off and retry
System.err.println("Rate limited: " + e.getMessage());
} catch (TurboDocxException.NetworkException e) {
// request never reached the server (DNS, refused, timeout)
System.err.println("Network error: " + e.getMessage());
} catch (TurboDocxException e) {
// catch-all for any other typed SDK error
System.err.println("Error " + e.getStatusCode() + ": " + e.getMessage());
}

Common Error Codes

StatusTypeWhen
400TurboDocxException.ValidationExceptionInvalid request body, missing required field
401TurboDocxException.AuthenticationExceptionMissing or invalid API key
403TurboDocxException.AuthorizationExceptionValid key without required role
404TurboDocxException.NotFoundExceptionQuote, product, company, contact, etc. not found
429TurboDocxException.RateLimitExceptionRate limit exceeded — back off and retry

Runnable End-to-End Examples

Three fully runnable examples live in the SDK repo:

Run any example after exporting TURBODOCX_API_KEY and TURBODOCX_ORG_ID.

See Also