A modular TypeScript SDK for seamless integration with Intuit QuickBooks APIs. Provides robust authentication handling and a future-ready foundation for accounting, payments, and commerce operations
- OAuth 2.0 Authentication: Simplified and secure authentication flow.
- Single Sign-On (SSO): Built-in OpenID Connect support for QuickBooks Single Sign-On with automatic ID token validation and user profile management.
- Token Management: Automatic refresh, rotation, and secure serialization/deserialization.
- API Coverage:
- Invoices
- Estimates
- Customers
- Payments
- Accounts
- CompanyInfo
- Bills
- Preferences
- Credit Memos
- Entity Management: Full CRUD operations with class-based entity methods:
- Create, update, and save entities
- Send entities via email (Invoice, Estimate, CreditMemo)
- Download entities as PDF (Invoice, Estimate, CreditMemo, Bill, Payment)
- Void transactions (Invoice, CreditMemo, Payment)
- Soft delete entities (Account, Customer)
- Type-Safe API: Full TypeScript declarations for all requests and responses.
- Pagination: Automatic handling of paginated responses.
- Environment Support: Supports both Production and Sandbox environments.
- Bun & Node.js Compatible: Optimized builds for both runtimes.
bun add quickbooks-api
# or
npm install quickbooks-apiimport { AuthProvider, Environment, AuthScopes } from 'quickbooks-api';
const authProvider = new AuthProvider('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET', 'YOUR_REDIRECT_URI', [
AuthScopes.Accounting,
AuthScopes.OpenId,
]);const authUrl = authProvider.generateAuthUrl();
// Redirect user to authUrl.toString()import type { UserAuthResponse } from 'quickbooks-api';
async function handleCallback(query: UserAuthResponse) {
try {
const token = await authProvider.exchangeCode(query.code, query.realmId);
console.log('Access Token:', token.accessToken);
} catch (error) {
console.error('Authentication failed:', error);
}
}import { ApiClient, Environment } from 'quickbooks-api';
const apiClient = new ApiClient(authProvider, Environment.Sandbox);The SDK includes built-in support for QuickBooks Single Sign-On using OpenID Connect, making it incredibly simple to authenticate users with their Intuit credentials.
Simply include the openid scope (and optionally profile, email, phone, address) when initializing the AuthProvider:
import { AuthProvider, AuthScopes } from 'quickbooks-api';
const authProvider = new AuthProvider('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET', 'YOUR_REDIRECT_URI', [
AuthScopes.Accounting,
AuthScopes.OpenId, // Required for SSO
AuthScopes.Profile, // Optional: Get user profile info
AuthScopes.Email, // Optional: Get user email
AuthScopes.Phone, // Optional: Get user phone
AuthScopes.Address, // Optional: Get user address
]);When you exchange the authorization code, the SDK automatically:
- Decodes and validates the ID token
- Fetches the user profile (if
profilescope is included) - Stores everything in the token for easy access
// Exchange code - SSO is handled automatically!
const token = await authProvider.exchangeCode(code, realmId);
// Access user profile
if (authProvider.isSsoEnabled()) {
const userProfile = await authProvider.getCurrentUserProfile();
console.log('User:', userProfile?.name);
console.log('Email:', userProfile?.email);
console.log('Email Verified:', userProfile?.emailVerified);
}
// Access ID token claims
if (token.idToken) {
console.log('User ID:', token.idToken.claims.sub);
console.log('Issuer:', token.idToken.claims.iss);
}You can also manually fetch the user profile at any time:
const userProfile = await authProvider.getUserProfile();
console.log('User Profile:', userProfile);The SDK automatically validates ID tokens, but you can also validate them manually:
// Decode and validate an ID token
const idToken = await authProvider.decodeIdToken(idTokenString);
const isValid = await authProvider.validateIdToken(idToken, nonce);if (authProvider.isSsoEnabled()) {
console.log('SSO is enabled');
}That's it! SSO is fully integrated and works seamlessly with the existing OAuth flow.
import { ApiClient, Environment, InvoiceStatus, InvoiceOptions } from 'quickbooks-api';
// Example: Get all invoices (with search options and pagination)
let hasNextPage = true;
let page = 1;
const paginatedInvoices = [];
while (hasNextPage) {
// Setup the Invoice
const invoiceOptions: InvoiceOptions = {
searchOptions: {
maxResults: 10,
page: page,
orderBy: { field: 'Id', direction: 'DESC' },
},
};
// Get the Invoices
const searchResponse = await apiClient.invoices.getAllInvoices(invoiceOptions);
// Add the Invoices to the List
paginatedInvoices.push(...searchResponse.results);
// Check if there is a next page
hasNextPage = searchResponse.hasNextPage;
// Log the Intuit Transaction ID for tracking
console.log('Transaction ID:', searchResponse.intuitTID);
// Increment the Page
page++;
}
// Get the list of Paid Invoice
const paidInvoices = await apiClient.invoices.getAllInvoices({ status: InvoiceStatus.Paid });
console.log('Transaction ID:', paidInvoices.intuitTID);The SDK uses a custom QuickbooksError class that extends the standard Error class and provides structured access to Intuit API error
details.
import { QuickbooksError } from 'quickbooks-api';
try {
const { invoice, intuitTID } = await apiClient.invoices.getInvoiceById('invalid-id');
if (invoice) {
console.log('Invoice ID:', invoice.Id);
console.log('Transaction ID:', intuitTID);
}
} catch (error) {
if (error instanceof QuickbooksError) {
// Access the error message
console.error('Error:', error.message);
// Access structured error details
console.error('Status Code:', error.details.statusCode);
console.error('Transaction ID:', error.details.intuitTID);
// Access Intuit-specific error information
error.details.intuitError.forEach((err) => {
console.error('Error Code:', err.code);
console.error('Error Message:', err.message);
console.error('Error Detail:', err.detail);
});
}
}The QuickbooksError includes the following properties:
message: string- Human-readable error messagedetails.statusCode: number- HTTP status code from the API responsedetails.intuitError: Array<{message: string, detail: string, code: string}>- Array of Intuit API error objectsdetails.intuitTID: string- Intuit transaction ID for support tracking
import { ApiClient, QuickbooksError } from 'quickbooks-api';
async function fetchAccount(accountId: string) {
try {
const { account, intuitTID } = await apiClient.accounts.getAccountById(accountId);
if (account) {
console.log('Account retrieved:', account.Name);
console.log('Transaction ID:', intuitTID);
return account;
}
return null;
} catch (error) {
if (error instanceof QuickbooksError) {
// Handle specific error cases
if (error.details.statusCode === 404) {
console.error('Account not found');
} else if (error.details.statusCode === 401) {
console.error('Authentication failed - token may be expired');
} else {
// Log full error details for debugging
console.error('QuickBooks API Error:', {
message: error.message,
statusCode: error.details.statusCode,
intuitErrors: error.details.intuitError,
transactionId: error.details.intuitTID,
});
}
} else {
// Handle unexpected errors
console.error('Unexpected error:', error);
}
throw error;
}
}All API methods now include the Intuit Transaction ID (intuitTID) in their responses for better request tracking and support debugging.
Methods that return multiple results (like getAllInvoices(), getAllAccounts(), etc.) return a SearchResponse object:
interface SearchResponse<T> {
results: Array<T>;
hasNextPage?: boolean;
intuitTID: string; // Intuit Transaction ID
}Example:
const response = await apiClient.invoices.getAllInvoices();
console.log('Invoices:', response.results);
console.log('Has next page:', response.hasNextPage);
console.log('Transaction ID:', response.intuitTID);Methods that return a single item (like getInvoiceById(), getAccountById(), etc.) return an object with the item and intuitTID:
// For most entities
const { invoice, intuitTID } = await apiClient.invoices.getInvoiceById('123');
if (invoice) {
console.log('Invoice ID:', invoice.Id);
console.log('Transaction ID:', intuitTID);
}
// For Company Info
const { companyInfo, intuitTID } = await apiClient.companyInfo.getCompanyInfo();
console.log('Company Name:', companyInfo.CompanyName);
console.log('Transaction ID:', intuitTID);Affected Methods:
getAccountById()getBillById()getCreditMemoById()getCustomerById()getEstimateById()getInvoiceById()getPaymentById()getCompanyInfo()rawCompanyInfoQuery()
The intuitTID is useful for:
- Support Requests: Provide the transaction ID when contacting Intuit support
- Debugging: Track specific API requests in logs
- Audit Trails: Maintain records of API interactions
All entity classes provide instance methods for managing QuickBooks entities. After retrieving an entity using get*ById() methods, you can
perform various operations on the entity instance.
The save() method handles both creating new entities and updating existing ones:
// Get an invoice
const { invoice, intuitTID } = await apiClient.invoices.getInvoiceById('123');
if (invoice) {
// Modify the invoice
invoice.PrivateNote = 'Updated note';
// Save changes (update)
await invoice.save();
// Or create a new invoice
const newInvoice = new Invoice(apiClient, {
Line: [...],
CustomerRef: { value: '456' },
// ... other required fields
});
await newInvoice.save(); // Creates new invoice
}Send invoices, estimates, and credit memos directly via email:
// Send an invoice via email
const { invoice } = await apiClient.invoices.getInvoiceById('123');
if (invoice) {
await invoice.send();
console.log('Invoice sent successfully');
}
// Send an estimate
const { estimate } = await apiClient.estimates.getEstimateById('456');
if (estimate) {
await estimate.send();
console.log('Estimate sent successfully');
}
// Send a credit memo
const { creditMemo } = await apiClient.creditMemos.getCreditMemoById('789');
if (creditMemo) {
await creditMemo.send();
console.log('Credit memo sent successfully');
}Download invoices, estimates, credit memos, bills, and payments as PDF files:
// Download invoice PDF
const { invoice } = await apiClient.invoices.getInvoiceById('123');
if (invoice) {
const pdfBlob = await invoice.downloadPDF();
// Save to file (Node.js example)
const fs = require('fs').promises;
await fs.writeFile('invoice.pdf', Buffer.from(await pdfBlob.arrayBuffer()));
// Or use in browser
const url = URL.createObjectURL(pdfBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'invoice.pdf';
link.click();
}
// Download estimate PDF
const { estimate } = await apiClient.estimates.getEstimateById('456');
if (estimate) {
const pdfBlob = await estimate.downloadPDF();
// Handle PDF blob...
}
// Download credit memo, bill, or payment PDF
const { creditMemo } = await apiClient.creditMemos.getCreditMemoById('789');
if (creditMemo) {
const pdfBlob = await creditMemo.downloadPDF();
// Handle PDF blob...
}Void invoices, credit memos, and payments:
// Void an invoice
const { invoice } = await apiClient.invoices.getInvoiceById('123');
if (invoice && invoice.Balance > 0) {
await invoice.void();
console.log('Invoice voided successfully');
}
// Void a credit memo
const { creditMemo } = await apiClient.creditMemos.getCreditMemoById('456');
if (creditMemo && creditMemo.Balance > 0) {
await creditMemo.void();
console.log('Credit memo voided successfully');
}
// Void a payment
const { payment } = await apiClient.payments.getPaymentById('789');
if (payment && payment.TotalAmt > 0) {
await payment.void();
console.log('Payment voided successfully');
}Soft delete accounts and customers by setting Active=false:
// Soft delete an account
const { account } = await apiClient.accounts.getAccountById('123');
if (account && account.Active) {
await account.delete(); // Sets Active=false
console.log('Account deactivated');
// Reactivate if needed
account.Active = true;
await account.save();
}
// Soft delete a customer
const { customer } = await apiClient.customers.getCustomerById('456');
if (customer && customer.Active) {
await customer.delete(); // Sets Active=false
console.log('Customer deactivated');
// Reactivate if needed
customer.Active = true;
await customer.save();
}Refresh entity data from the API:
// Reload invoice data
const { invoice } = await apiClient.invoices.getInvoiceById('123');
if (invoice) {
// Make local changes
invoice.PrivateNote = 'Local change';
// Reload from API to get latest data
await invoice.reload();
console.log('Invoice data refreshed');
}All entity methods throw QuickbooksError with detailed information:
try {
const { invoice } = await apiClient.invoices.getInvoiceById('123');
if (invoice) {
// This will throw if invoice doesn't have an ID
await invoice.downloadPDF();
}
} catch (error) {
if (error instanceof QuickbooksError) {
console.error('PDF download failed:', error.message);
console.error('Error details:', error.details);
}
}The AuthProvider handles token refresh, revocation, and secure storage. Use the following methods:
refresh(): Refreshes the access token.revoke(): Revokes the access token.serializeToken(secretKey: string): Securely encrypts and serializes the token for storage.deserializeToken(serialized: string, secretKey: string): Decrypts and restores the serialized token.
Important: Store tokens securely using serializeToken and deserializeToken.
The ApiClient provides access to the following APIs:
apiClient.invoices: Invoices APIapiClient.estimates: Estimates APIapiClient.customers: Customers APIapiClient.payments: Payments APIapiClient.accounts: Accounts APIapiClient.preferences: Preferences APIapiClient.creditMemos: Credit Memos APIapiClient.companyInfo: Company Info APIapiClient.bills: Bills API
Refer to the individual API documentation for available methods and options.
Join our Discord server for community support and discussions.
- Secure Token Storage: Never store tokens in client-side storage. Use secure server-side storage and the provided
serializeTokenanddeserializeTokenmethods. - Strong Secret Key: Use a strong, randomly generated secret key (minimum 32 characters) for token serialization. Rotate the key regularly.
- HTTPS: Always use HTTPS in production environments.
See CONTRIBUTING.md
This project is not affiliated with, endorsed by, or in any way officially connected with Intuit Inc., QuickBooks®, or any related subsidiaries. All trademarks and registered trademarks are the property of their respective owners.
QuickBooks® is a registered trademark of Intuit Inc., registered in the United States and other countries.