We’re excited to announce a major update to conductor-node that brings full support for Conductor’s new QuickBooks Desktop API v2. This new version introduces significant improvements in usability, consistency, and functionality.
Though the old version of conductor-node will continue to work, it will not receive any further updates. We strongly recommend upgrading to take advantage of the new features and improvements.

Key improvements

Our new QuickBooks Desktop API v2 has been completely redesigned from the ground up. We’ve carefully chosen every field name, parameter, and method description to be significantly more intuitive and clearer than QuickBooks’s original documentation. The API structure has been simplified while maintaining full functionality. Major improvements include:
  • Automatic pagination support
  • Automatic retries with exponential backoff
  • Configurable timeouts
  • Improved parameter and fields names
  • Completely rewritten inline documentation
  • New methods like .retrieve() and .delete()
  • All fields included in responses
  • Consistent array handling

How to upgrade

npm install conductor-node@latest

Breaking changes

No QuickBooks or Conductor business logic has changed! We’ve simply made the interface simpler, more intuitive, and more robust, while adding new features like automatic pagination and retries.

⚠️ Functional Changes

Several less common QuickBooks Desktop types/endpoints from the previous version of conductor-node are not yet available in the new SDK. If the type is listed in the left sidebar of this page, it is supported in the new SDK.Need a missing QBD type? Contact us and we’ll add it ASAP.
Previously, the API would return all matching records by default. This could lead to performance issues when unintentionally retrieving thousands of records, potentially causing long response times, overloading QuickBooks Desktop, and consuming excessive memory.To prevent these issues, Conductor now limits responses to a maximum of 150 records per page by default. This limit was carefully determined through extensive testing across different scenarios and system configurations to optimize performance while avoiding timeouts and resource constraints.With the new automatic pagination, you can efficiently retrieve all records when needed, without worrying about these performance concerns.

⚠️ Naming and Structure Changes

Old:
import Conductor from "conductor-node";

const conductor = new Conductor("{{YOUR_SECRET_KEY}}");
New: You can now automatically load your secret key from the CONDUCTOR_SECRET_KEY environment variable (or pass it to the constructor as an argument like before):
import Conductor from "conductor-node";

const conductor = new Conductor({
  apiKey: process.env["CONDUCTOR_SECRET_KEY"], // This is the default and can be omitted
});
Old: Singular resource names.
await conductor.qbd.bill.*
await conductor.qbd.invoice.*
await conductor.qbd.itemInventory.*
New: Plural resource names and clearer naming.
await conductor.qbd.bills.*
await conductor.qbd.invoices.*
await conductor.qbd.inventoryItems.*
Old: Odd naming conventions.
await conductor.qbd.bill.query(...)
await conductor.qbd.bill.add(...)
await conductor.qbd.bill.mod(...)
New: Intuitive naming following REST conventions.
await conductor.qbd.bills.list(...)
await conductor.qbd.bills.create(...)
await conductor.qbd.bills.update(...)
await conductor.qbd.bills.retrieve(...) // New!
await conductor.qbd.bills.delete(...) // New!
Old: Required as its own argument.
await conductor.qbd.bill.query("end_usr_1234abcd", { ... });
New: Required as a conductorEndUserId parameter in the parameters object.
await conductor.qbd.bills.list({
  conductorEndUserId: "end_usr_1234abcd",
  // ... other params
});
All request parameters are now in camelCase and have been renamed for clarity and consistency. Most of the changes are straightforward that you can find using autocomplete in your IDE or checking the APIs in the sidebar.
Old: Odd naming conventions.
await conductor.qbd.bill.add("end_usr_1234abcd", {
  VendorRef: { ListID: "..." },
  RefNumber: "...",
  ExpenseLineAdd: [
    {
      Amount: "1.00",
    },
  ],
});
New: camelCase and clearer naming.
await conductor.qbd.bills.create({
  conductorEndUserId: "end_usr_1234abcd",
  vendorId: "...",
  refNumber: "...",
  expenseLines: [
    {
      amount: "1.00",
    },
  ],
});
All output fields are now in camelCase and have been renamed for clarity and consistency. Most of the changes are straightforward that you can find using autocomplete in your IDE or checking the APIs in the sidebar.
Here are some key renamed fields:
  • TxnID and ListIDid
  • TimeCreated and TimeModifiedcreatedAt and updatedAt
  • EditSequencerevisionNumber
  • *Ref fields (e.g., CustomerRef) → simplified (e.g., customer)
Old: Odd naming conventions.
const invoice = await conductor.qbd.invoice.query("end_usr_1234abcd", "...");
// {
//   TxnID: "...",
//   TimeCreated: "...",
//   TimeModified: "...",
//   EditSequence: "...",
//   TxnNumber: "...",
//   CustomerRef: {
//     ListID: "...",
//     FullName: "..."
//   }
// }
New: camelCase and clearer naming.
const invoice = await conductor.qbd.invoices.retrieve({
  conductorEndUserId: "end_usr_1234abcd",
  id: "...",
});
// {
//   id: "...",
//   objectType: "qbd_invoice",
//   createdAt: "...",
//   updatedAt: "...",
//   revisionNumber: "...",
//   transactionNumber: "...",
//   customer: {
//     id: "...",
//     fullName: "..."
//   }
// }
Old: Odd naming conventions and complex structure.
const invoices = await conductor.qbd.invoice.query("end_usr_1234abcd", {
  ModifiedDateRangeFilter: {
    FromModifiedDate: "...",
    ToModifiedDate: "...",
  },
  AccountFilter: {
    ListID: "...",
  },
  RefNumberFilter: "...",
  RefNumberRangeFilter: {
    FromRefNumber: "...",
    ToRefNumber: "...",
  },
  MaxReturned: 100,
});
New: Significantly simplified structure and clearer naming.
const invoices = await conductor.qbd.invoices.list({
  conductorEndUserId: "end_usr_1234abcd",
  updatedAfter: "...",
  updatedBefore: "...",
  accountId: "...",
  refNumber: "...",
  refNumberFrom: "...",
  refNumberTo: "...",
  limit: 100,
});
To support automatic pagination, the query/list method now returns results in an object wrapper with properties for pagination metadata.Old: Returns an array of results directly.
const invoices = await conductor.qbd.invoice.query("end_usr_1234abcd");
// [
//   {
//     TxnID: "...",
//     TimeCreated: "...",
//     ...
//   }
// ]
New: Returns results in an object wrapper with properties for pagination metadata.
const response = await conductor.qbd.invoices.list({
  conductorEndUserId: "end_usr_1234abcd",
});

console.log(response);
// {
//   nextCursor: "...",
//   hasMore: true,
//   remainingCount: 100,
//   data: [
//     {
//       id: "...",
//       objectType: "qbd_invoice",
//       createdAt: "...",
//       ...
//     }
//   ]
// }
Fields that can have multiple values are now always arrays, even when there’s only one record:
const response = await conductor.qbd.invoices.list({
  conductorEndUserId: "end_usr_1234abcd",
});

// All responses with potential multiple records use arrays consistently
console.log(response.data[0].lines); // Always an array, even if there's only one line

New features

You can now easily paginate through all results. See the automatic pagination section of the README for more details.
async function fetchAllQbdInvoices(params) {
  const allQbdInvoices = [];
  // Automatically fetches more pages as needed
  for await (const invoice of conductor.qbd.invoices.list({
    conductorEndUserId: "{{YOUR_END_USER_ID}}",
    ...params,
  })) {
    allQbdInvoices.push(invoice);
  }
  return allQbdInvoices;
}
Intelligently retries requests, but only those requests that can potentially succeed when retried (such as network errors).
All objects now have a retrieve method and all transaction objects have a delete method.
await conductor.qbd.bills.retrieve({ conductorEndUserId: "...", id: "..." });
await conductor.qbd.bills.delete({ conductorEndUserId: "...", id: "..." });
The new API now returns a consistent response structure with all possible fields, making it easier to work with the data. Fields that have no value are explicitly set to null rather than being omitted, and empty array fields return [] instead of being excluded. This means you can reliably access any field without checking if it exists first, and array operations will work consistently without special handling for missing fields.

More information

For more detailed information about the new Node.js SDK, please check its GitHub repository.