quickbooks-api
Version:
A modular TypeScript SDK for seamless integration with Intuit QuickBooks APIs. Provides robust authentication handling and future-ready foundation for accounting, payments, and commerce operations.
609 lines (465 loc) • 17.4 kB
Markdown
# QuickBooks API SDK



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.
## Key Features
- **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.
---
## Installation
```bash
bun add quickbooks-api
# or
npm install quickbooks-api
```
---
## Getting Started
### 1. Initialize the Auth Provider
```typescript
import { AuthProvider, Environment, AuthScopes } from 'quickbooks-api';
const authProvider = new AuthProvider('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET', 'YOUR_REDIRECT_URI', [
AuthScopes.Accounting,
AuthScopes.OpenId,
]);
```
### 2. Generate the Authorization URL
```typescript
const authUrl = authProvider.generateAuthUrl();
// Redirect user to authUrl.toString()
```
### 3. Handle the Callback
```typescript
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);
}
}
```
### 4. Initialize the API Client
```typescript
import { ApiClient, Environment } from 'quickbooks-api';
const apiClient = new ApiClient(authProvider, Environment.Sandbox);
```
---
## Single Sign-On (SSO) Support
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.
### Enable SSO
Simply include the `openid` scope (and optionally `profile`, `email`, `phone`, `address`) when initializing the AuthProvider:
```typescript
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
]);
```
### Automatic SSO Handling
When you exchange the authorization code, the SDK automatically:
- Decodes and validates the ID token
- Fetches the user profile (if `profile` scope is included)
- Stores everything in the token for easy access
```typescript
// 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);
}
```
### Manual User Profile Retrieval
You can also manually fetch the user profile at any time:
```typescript
const userProfile = await authProvider.getUserProfile();
console.log('User Profile:', userProfile);
```
### ID Token Validation
The SDK automatically validates ID tokens, but you can also validate them manually:
```typescript
// Decode and validate an ID token
const idToken = await authProvider.decodeIdToken(idTokenString);
const isValid = await authProvider.validateIdToken(idToken, nonce);
```
### Check SSO Status
```typescript
if (authProvider.isSsoEnabled()) {
console.log('SSO is enabled');
}
```
That's it! SSO is fully integrated and works seamlessly with the existing OAuth flow.
### 5. Make API Calls (with Invoice Options)
```typescript
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);
```
---
## Error Handling
The SDK uses a custom `QuickbooksError` class that extends the standard `Error` class and provides structured access to Intuit API error
details.
### QuickbooksError Structure
```typescript
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);
});
}
}
```
### Error Details
The `QuickbooksError` includes the following properties:
- `message: string` - Human-readable error message
- `details.statusCode: number` - HTTP status code from the API response
- `details.intuitError: Array<{message: string, detail: string, code: string}>` - Array of Intuit API error objects
- `details.intuitTID: string` - Intuit transaction ID for support tracking
### Example: Handling API Errors
```typescript
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;
}
}
```
---
## API Response Structure
All API methods now include the Intuit Transaction ID (`intuitTID`) in their responses for better request tracking and support debugging.
### Search/List Methods
Methods that return multiple results (like `getAllInvoices()`, `getAllAccounts()`, etc.) return a `SearchResponse` object:
```typescript
interface SearchResponse<T> {
results: Array<T>;
hasNextPage?: boolean;
intuitTID: string; // Intuit Transaction ID
}
```
**Example:**
```typescript
const response = await apiClient.invoices.getAllInvoices();
console.log('Invoices:', response.results);
console.log('Has next page:', response.hasNextPage);
console.log('Transaction ID:', response.intuitTID);
```
### Single Item Methods
Methods that return a single item (like `getInvoiceById()`, `getAccountById()`, etc.) return an object with the item and `intuitTID`:
```typescript
// 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()`
### Using Transaction IDs
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
---
## Entity Management
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.
### Saving Entities
The `save()` method handles both creating new entities and updating existing ones:
```typescript
// 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
}
```
### Sending Entities via Email
Send invoices, estimates, and credit memos directly via email:
```typescript
// 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');
}
```
### Downloading PDFs
Download invoices, estimates, credit memos, bills, and payments as PDF files:
```typescript
// 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...
}
```
### Voiding Transactions
Void invoices, credit memos, and payments:
```typescript
// 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 Deleting Entities
Soft delete accounts and customers by setting `Active=false`:
```typescript
// 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();
}
```
### Reloading Entity Data
Refresh entity data from the API:
```typescript
// 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');
}
```
### Error Handling with Entity Methods
All entity methods throw `QuickbooksError` with detailed information:
```typescript
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);
}
}
```
---
## Token Management
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`.
---
## API Reference
The `ApiClient` provides access to the following APIs:
- `apiClient.invoices`: Invoices API
- `apiClient.estimates`: Estimates API
- `apiClient.customers`: Customers API
- `apiClient.payments`: Payments API
- `apiClient.accounts`: Accounts API
- `apiClient.preferences`: Preferences API
- `apiClient.creditMemos`: Credit Memos API
- `apiClient.companyInfo`: Company Info API
- `apiClient.bills`: Bills API
Refer to the individual API documentation for available methods and options.
---
## Support
Join our [Discord server](https://discord.gg/zcdUNMRcQR) for community support and discussions.
---
## Security Considerations
- **Secure Token Storage:** Never store tokens in client-side storage. Use secure server-side storage and the provided `serializeToken` and
`deserializeToken` methods.
- **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.
---
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md)
---
## Legal Disclaimer
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.