Skip to main content

TurboQuote JavaScript / TypeScript 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 Node.js and browser applications. Build quoting and CPQ (configure-price-quote) workflows: create and send quotes, manage line items, maintain a product and bundle catalog, apply price books, and handle the full quote lifecycle — all with zero runtime dependencies and complete TypeScript types. Available on npm as @turbodocx/sdk (same package as TurboSign and TurboWebhooks).


What is TurboQuote?

TurboQuote is TurboDocx's quoting and CPQ module. Quotes progress through a lifecycle: draftpending_approvalsentaccepted / declined / voided. Each quote belongs to a company and contact, carries line items (individual products or bundles), and can optionally have a price book applied. Accepted quotes can be merged with a TurboDocx Deliverable (e.g. a contract generated from a template) and sent for e-signature through TurboSign via sendQuoteWithDeliverable.

Installation

npm install @turbodocx/sdk

Requirements

  • Node.js 18 or higher (native fetch)
  • TypeScript 4.7+ (optional, for type checking — declaration files are included)
  • Zero runtime dependencies — the SDK uses only Node built-ins

Configuration

import { TurboQuote } from '@turbodocx/sdk';

TurboQuote.configure({
apiKey: process.env.TURBODOCX_API_KEY!,
orgId: process.env.TURBODOCX_ORG_ID, // optional — falls back to env var
});
No senderEmail required

Unlike TurboSign, TurboQuote.configure() does not require senderEmail or senderName — quotes are not sent as signature emails. Only apiKey is required; orgId is recommended but falls back to TURBODOCX_ORG_ID. If you skip configure() entirely, the SDK auto-initialises from environment variables on the first method call.

Environment Variables

TURBODOCX_API_KEY=your_api_key_here
TURBODOCX_ORG_ID=your_org_id_here
# optional — defaults to https://api.turbodocx.com
TURBODOCX_BASE_URL=https://api.turbodocx.com

Quick Start

The most common flow: create a quote for a company and contact, add a product line item, then send it.

import { TurboQuote } from '@turbodocx/sdk';
import { writeFileSync } from 'node:fs';

TurboQuote.configure({
apiKey: process.env.TURBODOCX_API_KEY!,
orgId: process.env.TURBODOCX_ORG_ID,
});

// 1. Create a draft quote
const quote = await TurboQuote.createQuote({
name: 'Acme Corp — Enterprise Plan',
companyId: 'company-uuid',
contactId: 'contact-uuid',
currency: 'USD',
termDays: 30,
});

// 2. Add a product line item
await TurboQuote.addLineItems(quote.id, {
productId: 'product-uuid',
productName: 'Enterprise Licence',
unitPrice: 1200,
billingFrequency: 'annual',
quantity: 5,
});

// 3. Send the quote
const { quote: sentQuote, message } = await TurboQuote.sendQuote(quote.id, {
validUntil: '2026-07-31',
});
console.log(message); // "Quote sent successfully"
console.log(sentQuote.status); // "sent"

// 4. Download the PDF
const pdf = await TurboQuote.downloadQuotePdf(sentQuote.id);
writeFileSync('quote.pdf', Buffer.from(pdf));

Convenience: createAndSend

createAndSend combines quote creation, line item addition, and sending into a single call.

const { quote } = await TurboQuote.createAndSend({
name: 'Acme Corp — Starter',
companyId: 'company-uuid',
contactId: 'contact-uuid',
currency: 'USD',
termDays: 30,
items: [
{ productId: null, productName: 'Setup Fee', unitPrice: 500, billingFrequency: 'one-time' },
{ productId: 'product-uuid', productName: 'Monthly Subscription', unitPrice: 99, billingFrequency: 'monthly', quantity: 10 },
],
send: { validUntil: '2026-07-31' },
});
console.log(quote.status); // "sent"

Method Reference

All methods are static on the TurboQuote class. Configure once, then call on the class directly.

Quotes

listQuotes

List quotes with optional pagination and filters. Returns totals and pipeline stats alongside results.

const { results, totalRecords, stats } = await TurboQuote.listQuotes({
limit: 20,
offset: 0,
statuses: ['draft', 'sent'], // string or string[]
companyId: 'company-uuid',
currency: 'USD',
});
// stats.total, stats.winRate, stats.monthlyRecurringRevenue, ...

createQuote

Create a new quote in draft status.

// Fixed-term quote — termDays 1–3650, no renewalPeriod (0 = one-time, -1 = auto-renewal)
const quote = await TurboQuote.createQuote({
name: 'Q3 Renewal', // required
companyId: 'company-uuid', // required
contactId: 'contact-uuid', // required
currency: 'USD', // 'USD'|'EUR'|'GBP'|'CAD'|'AUD'|'INR'
termDays: 30, // fixed term in days
validUntil: '2026-09-30',
taxRate: 8.5,
priceBookId: 'pb-uuid',
});

// Auto-renewal quote — termDays: -1 REQUIRES renewalPeriod, and renewalPeriod is ONLY valid
// when termDays is -1. Pairing renewalPeriod with a fixed term (e.g. termDays: 30) returns a 400.
const subscription = await TurboQuote.createQuote({
name: 'Annual Subscription',
companyId: 'company-uuid',
contactId: 'contact-uuid',
currency: 'USD',
termDays: -1, // -1 = auto-renewal
renewalPeriod: 'annually', // 'weekly'|'monthly'|'quarterly'|'annually'
});

getQuote

Fetch a single quote. The returned object includes a statusInfo field with transition flags (canSend, canAccept, canDecline, canVoid).

const quote = await TurboQuote.getQuote('quote-uuid');
console.log(quote.statusInfo?.canSend); // true when status is 'draft'

updateQuote

Patch any combination of quote fields. Pass null to clear nullable fields (renewalPeriod, validUntil, taxRate, priceBookId).

const updated = await TurboQuote.updateQuote('quote-uuid', {
name: 'Q3 Renewal — Revised',
taxRate: null, // clears the tax rate
});

deleteQuote

Soft-delete a quote.

const { message } = await TurboQuote.deleteQuote('quote-uuid');

duplicateQuote

Copy a quote (and its line items) into a new draft.

const copy = await TurboQuote.duplicateQuote('quote-uuid');

applyPriceBook

Apply a price book to all line items on a quote. Returns the updated quote plus counts of how many items were updated vs skipped.

const { quote, updatedCount, skippedCount, message } = await TurboQuote.applyPriceBook(
'quote-uuid',
'pricebook-uuid',
);

removePriceBook

Detach the price book from a quote (line item prices are not reverted).

const quote = await TurboQuote.removePriceBook('quote-uuid');

downloadQuotePdf

Download the quote as a PDF. Returns raw bytes as an ArrayBuffer.

const pdf = await TurboQuote.downloadQuotePdf('quote-uuid');
writeFileSync('quote.pdf', Buffer.from(pdf));

Quote Status Transitions

sendQuote

Send a draft quote to the contact. Optionally include CC recipients or set a validity deadline.

const { quote, message } = await TurboQuote.sendQuote('quote-uuid', {
validUntil: '2026-07-31',
ccEmails: ['manager@example.com'],
});

sendQuoteWithDeliverable

Send a quote paired with a TurboDocx deliverable (e.g., a contract generated from a template). The deliverable is merged before or after the quote PDF.

const { quote, message, documentId } = await TurboQuote.sendQuoteWithDeliverable(
'quote-uuid',
{
deliverableId: 'deliverable-uuid',
mergePosition: 'end', // 'beginning' | 'end'
ccEmails: ['legal@example.com'],
},
);
// documentId — TurboSign document created for the merged PDF

declineQuote

Mark a sent quote as declined (typically called on behalf of the recipient).

const quote = await TurboQuote.declineQuote('quote-uuid', {
reason: 'Budget constraints for this quarter',
});

voidQuote

Void a quote that should no longer be valid.

const quote = await TurboQuote.voidQuote('quote-uuid', {
reason: 'Superseded by revised quote #Q-102',
});

handleExpiredQuote

Handle a quote that has passed its validUntil date. Choose to void or decline it and optionally extend with a new validity date.

const quote = await TurboQuote.handleExpiredQuote('quote-uuid', {
action: 'void', // 'void' | 'decline'
reason: 'Expired — re-quoting',
newValidUntil: '2026-08-31',
});

Line Items

Line items attach products or bundles to a quote, each with a price, quantity, billing frequency, and optional discount.

listLineItems

const { results, totalRecords } = await TurboQuote.listLineItems('quote-uuid', {
limit: 50,
billingFrequency: 'monthly',
});

addLineItems

Add one or more product line items. Pass a single object or an array.

// Single item
await TurboQuote.addLineItems('quote-uuid', {
productId: 'product-uuid', // null for a custom (freeform) line item
productName: 'Professional Services',
unitPrice: 150,
billingFrequency: 'one-time',
quantity: 8,
discountPercent: 10,
discountType: 'percent',
});

// Multiple items at once
await TurboQuote.addLineItems('quote-uuid', [
{ productId: 'p1', productName: 'Licence A', unitPrice: 500, billingFrequency: 'annual' },
{ productId: 'p2', productName: 'Licence B', unitPrice: 300, billingFrequency: 'annual' },
]);

addBundleLineItems

Add one or more bundle line items.

await TurboQuote.addBundleLineItems('quote-uuid', {
bundleId: 'bundle-uuid',
bundleName: 'Starter Bundle',
quantity: 2,
showItemsToEndUser: true,
});

updateLineItem

Update a single line item's price, quantity, discount, or billing frequency.

const updated = await TurboQuote.updateLineItem('quote-uuid', 'item-uuid', {
quantity: 12,
discountPercent: 15,
billingFrequency: 'monthly',
});

removeLineItem

Remove a line item from a quote.

const { message } = await TurboQuote.removeLineItem('quote-uuid', 'item-uuid');

Products

Manage your product catalog. Products can include images (uploaded as multipart form data — the SDK detects the MIME type from magic bytes automatically).

MethodSignatureReturns
listProducts(opts?) → ProductListResponsePaginated list + catalog stats
createProduct(req) → ProductCreated product
getProduct(id) → ProductSingle product
updateProduct(id, req) → ProductUpdated product
deleteProduct(id) → SuccessResponseMessage
duplicateProduct(id) → ProductNew duplicate
getProductPrimaryImages(productIds[]) → { [id]: ProductImage | null }Primary image map
// Create a product with an image
const product = await TurboQuote.createProduct({
name: 'Enterprise Licence',
listPrice: 1200,
billingFrequency: 'annual',
categoryId: 'category-uuid',
sku: 'ENT-001',
showInCatalog: true,
images: ['/path/to/product-image.png'], // file path, Buffer, or File object
});

// List with filters
const { results, totalProducts } = await TurboQuote.listProducts({
categoryIds: ['cat-1', 'cat-2'],
billingFrequency: 'monthly',
showInCatalog: true,
});

// Fetch primary images for multiple products at once
const images = await TurboQuote.getProductPrimaryImages(['p-uuid-1', 'p-uuid-2']);
// images['p-uuid-1'] → ProductImage | null

Bundles

Bundles group multiple products into a single purchasable unit with optional bundle-level discounts.

MethodSignatureReturns
listBundles(opts?) → BundleListResponsePaginated list + stats
createBundle(req) → BundleCreated bundle
getBundle(id) → BundleSingle bundle
updateBundle(id, req) → BundleUpdated bundle
deleteBundle(id) → SuccessResponseMessage
duplicateBundle(id) → BundleNew duplicate
const bundle = await TurboQuote.createBundle({
name: 'Starter Pack',
categoryId: 'category-uuid',
currency: 'USD',
showInCatalog: true,
syncWithProducts: true,
items: [
{ productId: 'p1', unitPrice: 200, billingFrequency: 'monthly', quantity: 1 },
{ productId: 'p2', unitPrice: 50, billingFrequency: 'monthly', quantity: 3 },
],
});

Price Books

Price books let you define alternative pricing tiers. When a price book is applied to a quote, matching product line items are repriced automatically.

MethodSignatureReturns
listPriceBooks(opts?) → PriceBookListResponsePaginated list + stats
createPriceBook(req) → PriceBookCreated price book
getPriceBook(id) → PriceBookSingle price book
updatePriceBook(id, req) → PriceBookUpdated price book
deletePriceBook(id) → SuccessResponseMessage
duplicatePriceBook(id) → PriceBookNew duplicate
listPriceBookProducts(id, opts?) → PaginatedResponse<PriceBookProductPricing>Per-product pricing
const pb = await TurboQuote.createPriceBook({
name: 'Partner Tier',
priceBookTypeId: 'type-uuid',
validFrom: '2026-01-01',
validTo: '2026-12-31',
discountPercent: 20,
isDefault: false,
showInQuoteBuilder: true,
productPricing: [
{ productId: 'p1', discountPercent: 25, finalPrice: 900 },
],
});

// Apply to a quote
const { updatedCount, skippedCount } = await TurboQuote.applyPriceBook(
'quote-uuid',
pb.id,
);

Companies

Companies represent the buyer organisations your quotes are addressed to. Each company must have at least one contact.

MethodSignatureReturns
listCompanies(opts?) → CompanyListResponsePaginated list
createCompany(req) → CompanyCreated company
getCompany(id) → CompanySingle company
updateCompany(id, req) → CompanyUpdated company
deleteCompany(id) → SuccessResponseMessage
listCompanyContacts(companyId, opts?) → ContactListResponseCompany's contacts
const company = await TurboQuote.createCompany({
name: 'Acme Corporation',
phone: '+1-555-0100',
city: 'San Francisco',
state: 'CA',
country: 'US',
contacts: [
{ name: 'Alice Smith', email: 'alice@acme.example', title: 'VP of Engineering' },
],
});

const { results: contacts } = await TurboQuote.listCompanyContacts(company.id);

Contacts

Contacts belong to a company and are the individuals a quote is addressed to.

No getContact endpoint

There is no getContact(id) method — the backend does not expose a GET /v1/contacts/:id route. Retrieve an individual contact via listContacts with a search query, or fetch all contacts for a company with listCompanyContacts.

MethodSignatureReturns
listContacts(opts?) → ContactListResponsePaginated list
createContact(req) → ContactCreated contact
updateContact(id, req) → ContactUpdated contact
deleteContact(id) → SuccessResponseMessage
const contact = await TurboQuote.createContact({
name: 'Bob Jones',
companyId: 'company-uuid',
email: 'bob@acme.example',
title: 'Procurement Manager',
});

const { results } = await TurboQuote.listContacts({ companyId: 'company-uuid' });

Quote Templates

Quote templates control the visual presentation of the sent quote (logo, brand colours, disclaimer, terms, sender info). There is one active template per org, accessible via getTemplate(). You can also manage named templates.

Singleton vs. list

getTemplate() returns the org's single active template via GET /v1/quote-template (singular path). listTemplates() returns all named templates via GET /v1/quote-templates (plural path).

MethodSignatureReturns
getTemplate() → QuoteTemplateActive org template
listTemplates(opts?) → QuoteTemplateListResponseAll named templates
getTemplateById(id) → QuoteTemplateNamed template by ID
createTemplate(req) → QuoteTemplateCreated template
updateTemplate(id, req) → QuoteTemplateUpdated template
deleteTemplate(id) → SuccessResponseMessage
// Fetch the active template
const tmpl = await TurboQuote.getTemplate();
console.log(tmpl.primaryColor); // e.g. "#1a73e8"

// Create a named template
const branded = await TurboQuote.createTemplate({
logoUrl: 'https://cdn.example.com/logo.png',
primaryColor: '#0057b8',
primaryTextColor: '#ffffff',
disclaimer: 'Prices valid for 30 days.',
termsAndConditions: 'See attached terms...',
senderName: 'TurboDocx Sales',
senderEmail: 'sales@example.com',
});

Types (Categories)

Types are reusable category/classification values used across products, bundles, price books, and companies (e.g., industry tags, price book types).

No getType endpoint

There is no getType(id) method — the backend does not expose GET /v1/types/:id. Use listTypes to retrieve individual types.

MethodSignatureReturns
listTypes(opts?) → QuoteTypeListResponsePaginated list
createType(req) → QuoteTypeCreated type
updateType(id, req) → QuoteTypeUpdated type
deleteType(id) → SuccessResponseMessage
const type = await TurboQuote.createType({
name: 'Software',
categoryType: 'product_category', // 'product_category'|'pricebook_type'|'company_industry'|'bundle_category'
});

const { results } = await TurboQuote.listTypes({
categoryType: 'company_industry',
includeUsage: true,
});

TypeScript Types

Key types exported from @turbodocx/sdk:

import type {
// Core quote
Quote,
QuoteStatusInfo,
CreateQuoteRequest,
UpdateQuoteRequest,
ListQuotesOptions,
QuoteListResponse,
SendQuoteRequest,
SendQuoteWithDeliverableRequest,
DeclineQuoteRequest,
VoidQuoteRequest,
HandleExpiredQuoteRequest,
ApplyPriceBookResponse,
CreateAndSendRequest,
// Line items
LineItem,
AddLineItemRequest,
AddBundleLineItemRequest,
UpdateLineItemRequest,
ListLineItemsOptions,
// Products
Product,
CreateProductRequest,
UpdateProductRequest,
ListProductsOptions,
// Bundles
Bundle,
CreateBundleRequest,
UpdateBundleRequest,
// Price books
PriceBook,
CreatePriceBookRequest,
// Companies & contacts
Company,
CreateCompanyRequest,
Contact,
CreateContactRequest,
// Templates & types
QuoteTemplate,
QuoteType,
// Enums
QuoteStatus,
BillingFrequency,
Currency,
RenewalPeriod,
LineItemType,
DiscountType,
CategoryType,
// Shared
SuccessResponse,
} from '@turbodocx/sdk';

Key Enum Values

TypeValues
QuoteStatus'draft' 'pending_approval' 'sent' 'accepted' 'declined' 'voided'
BillingFrequency'monthly' 'quarterly' 'annual' 'one-time'
Currency'USD' 'EUR' 'GBP' 'CAD' 'AUD' 'INR'
RenewalPeriod'weekly' 'monthly' 'quarterly' 'annually'
DiscountType'percent' 'amount'
CategoryType'product_category' 'pricebook_type' 'company_industry' 'bundle_category'

Error Handling

import {
TurboQuote,
TurboDocxError,
AuthenticationError,
AuthorizationError,
ValidationError,
NotFoundError,
RateLimitError,
NetworkError,
} from '@turbodocx/sdk';

try {
await TurboQuote.sendQuote('quote-uuid');
} catch (e) {
if (e instanceof ValidationError) {
// 400 — e.g. quote is not in draft status, missing required fields
console.error('Validation failed:', e.message);
} else if (e instanceof NotFoundError) {
// 404 — quote, company, contact, or product does not exist
console.error('Not found:', e.message);
} else if (e instanceof AuthenticationError) {
// 401 — bad or revoked API key
console.error('Auth failed:', e.message);
} else if (e instanceof AuthorizationError) {
// 403 — API key does not have permission for this org
console.error('Forbidden:', e.message);
} else if (e instanceof RateLimitError) {
// 429 — back off and retry
console.error('Rate limited — retry after a moment');
} else if (e instanceof NetworkError) {
// request never reached the server
console.error('Network error:', e.message);
} else if (e instanceof TurboDocxError) {
// catch-all for any other typed SDK error
console.error(`Error ${e.statusCode}: ${e.message}`);
} else {
throw e;
}
}

Common Error Codes

StatusClassWhen
400ValidationErrorInvalid request body, wrong quote status for transition
401AuthenticationErrorMissing or invalid API key
403AuthorizationErrorValid key without permission for this resource
404NotFoundErrorQuote, company, product, or contact not found
429RateLimitErrorRate limit exceeded — back off and retry

Runnable Examples

Validated end-to-end examples live in the SDK repo:

Run any example with:

export TURBODOCX_API_KEY=your_key
export TURBODOCX_ORG_ID=your_org_id
npx tsx examples/turboquote-basic.ts

See Also