How to Export QuickBooks Desktop Invoices via API

Learn how to export QuickBooks Desktop or QuickBooks Enterprise invoices via API with incremental sync, durable IDs, and downstream upserts.

If you want to export QuickBooks Desktop invoices via API, the cleanest approach is to use Conductor on the QuickBooks Desktop side and then write the exported data into your app, warehouse, or downstream system.

This is one of the most common QuickBooks Desktop integration workflows, and it is one of the most valuable to get right.

Quick answers

  • Can I export invoices from QuickBooks Desktop via API? Yes.

  • Does this work for QuickBooks Enterprise too? Yes.

  • What is the main pattern? Check connection health, fetch invoices incrementally, store the QuickBooks invoice ID, and upsert downstream.

  • Why use Conductor? Conductor gives you a modern API for the QuickBooks Desktop side instead of forcing you to build directly on qbXML and Web Connector.

Invoices are one of the highest-value accounting objects because they are useful for:

  • syncing billing data into your app

  • powering revenue dashboards

  • exporting receivables data into a warehouse

  • mirroring invoice state into CRM or support systems

  • reconciling upstream product activity with downstream accounting records

That also means invoice export needs to be done carefully. If you do not store durable IDs and incremental sync state, it is easy to create duplicate downstream records or miss updates.

The recommended export pattern

For most teams, the right pattern is:

  1. check QuickBooks Desktop connection health

  2. request invoices updated after your last successful sync

  3. store the QuickBooks invoice ID as the durable source-side key

  4. upsert the data into your downstream system

  5. save the latest sync cursor

That is much better than re-exporting the full invoice table every time.

Example: export invoices with Conductor

import Conductor from "conductor-node";

const conductor = new Conductor({
  apiKey: process.env.CONDUCTOR_SECRET_KEY!,
});

const conductorEndUserId = process.env.CONDUCTOR_END_USER_ID!;

async function exportInvoices(updatedAfter: string) {
  await conductor.qbd.healthCheck({ conductorEndUserId });

  const invoices = await conductor.qbd.invoices.list({
    conductorEndUserId,
    updatedAfter,
    limit: 100,
  });

  return invoices.data.map((invoice) => ({
    quickbooksInvoiceId: invoice.id,
    refNumber: invoice.refNumber,
    customerId: invoice.customer?.id ?? null,
    totalAmount: invoice.totalAmount,
    updatedAt: invoice.updatedAt,
    txnDate: invoice.txnDate,
    balanceRemaining: invoice.balanceRemaining,
  }));
}

exportInvoices("2026-01-01T00:00:00Z").then(console.log).catch(console.error);
import Conductor from "conductor-node";

const conductor = new Conductor({
  apiKey: process.env.CONDUCTOR_SECRET_KEY!,
});

const conductorEndUserId = process.env.CONDUCTOR_END_USER_ID!;

async function exportInvoices(updatedAfter: string) {
  await conductor.qbd.healthCheck({ conductorEndUserId });

  const invoices = await conductor.qbd.invoices.list({
    conductorEndUserId,
    updatedAfter,
    limit: 100,
  });

  return invoices.data.map((invoice) => ({
    quickbooksInvoiceId: invoice.id,
    refNumber: invoice.refNumber,
    customerId: invoice.customer?.id ?? null,
    totalAmount: invoice.totalAmount,
    updatedAt: invoice.updatedAt,
    txnDate: invoice.txnDate,
    balanceRemaining: invoice.balanceRemaining,
  }));
}

exportInvoices("2026-01-01T00:00:00Z").then(console.log).catch(console.error);
import Conductor from "conductor-node";

const conductor = new Conductor({
  apiKey: process.env.CONDUCTOR_SECRET_KEY!,
});

const conductorEndUserId = process.env.CONDUCTOR_END_USER_ID!;

async function exportInvoices(updatedAfter: string) {
  await conductor.qbd.healthCheck({ conductorEndUserId });

  const invoices = await conductor.qbd.invoices.list({
    conductorEndUserId,
    updatedAfter,
    limit: 100,
  });

  return invoices.data.map((invoice) => ({
    quickbooksInvoiceId: invoice.id,
    refNumber: invoice.refNumber,
    customerId: invoice.customer?.id ?? null,
    totalAmount: invoice.totalAmount,
    updatedAt: invoice.updatedAt,
    txnDate: invoice.txnDate,
    balanceRemaining: invoice.balanceRemaining,
  }));
}

exportInvoices("2026-01-01T00:00:00Z").then(console.log).catch(console.error);

This gives you the core export loop most developers or coding agents need to start with.

Example: upsert invoices downstream

Once you fetch the invoices, you usually write them into a table keyed by the QuickBooks invoice ID.

CREATE TABLE quickbooks_invoices (
  quickbooks_invoice_id TEXT PRIMARY KEY,
  ref_number TEXT NULL,
  customer_id TEXT NULL,
  total_amount NUMERIC NULL,
  balance_remaining NUMERIC NULL,
  txn_date TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL
)

CREATE TABLE quickbooks_invoices (
  quickbooks_invoice_id TEXT PRIMARY KEY,
  ref_number TEXT NULL,
  customer_id TEXT NULL,
  total_amount NUMERIC NULL,
  balance_remaining NUMERIC NULL,
  txn_date TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL
)

CREATE TABLE quickbooks_invoices (
  quickbooks_invoice_id TEXT PRIMARY KEY,
  ref_number TEXT NULL,
  customer_id TEXT NULL,
  total_amount NUMERIC NULL,
  balance_remaining NUMERIC NULL,
  txn_date TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL
)

That durable ID is what makes later updates and incremental sync safe.

What fields matter most

The exact fields depend on your product, but the most common useful invoice export fields are:

  • invoice ID

  • reference number

  • customer ID

  • transaction date

  • total amount

  • open balance

  • last updated timestamp

  • line items, when the downstream use case needs them

If the downstream system cares about invoice-level revenue only, you may not need every line-item field immediately. If it cares about product analytics or fulfillment mapping, you probably do.

Incremental sync is more important than full export

The simplest correct export flow is usually incremental.

That means:

  • save the last successful sync timestamp

  • request invoices updated after that point

  • upsert by QuickBooks invoice ID

  • keep a small replay buffer if you want extra safety

That is a much better operational pattern than full re-imports for every run.

Common mistakes

Avoid these mistakes:

  • exporting invoices without storing the QuickBooks invoice ID

  • doing full reloads every time instead of incremental sync

  • assuming invoice reference numbers are durable unique keys

  • ignoring invoice updates after the original export

  • treating QuickBooks Desktop like a cloud API that is always online

Where Conductor fits

Conductor is what makes invoice export practical for modern teams.

Instead of building directly on:

  • QuickBooks Web Connector

  • qbXML

  • Desktop session handling

  • Windows-machine support

your team can focus on:

  • invoice mapping

  • incremental sync logic

  • downstream storage

  • analytics and application features

Frequently asked questions

Does this work for QuickBooks Enterprise too?

Yes. QuickBooks Enterprise is still part of the broader QuickBooks Desktop stack.

Should I use invoice reference numbers as the primary key?

Usually no. Use the QuickBooks invoice ID as the durable source-side key. Reference numbers are useful, but they are not the safest identity field for export pipelines.

Should I export line items too?

Only if the downstream use case needs them. Start with invoice-level export if that is enough, then add line-item detail when the product or reporting workflow requires it.

Bottom line

The best way to export QuickBooks Desktop invoices via API is:

  • use Conductor to fetch invoices from the live company file

  • sync incrementally using updatedAfter

  • store the QuickBooks invoice ID as the durable key

  • upsert downstream instead of reloading everything

Related reading