LedgerCrossingLedgerCrossing Buy LedgerCrossing — $299

Connect Python to QuickBooks Desktop (2026)

Short answer: QuickBooks Desktop has no web API. Every route in ends at Intuit's qbXML SDK on the Windows machine where QuickBooks runs: the Web Connector, ODBC drivers, and the COM request processor all do. LedgerCrossing runs on that machine and gives it an HTTP endpoint. Python sends a qbXML request over HTTP (with an API key when it calls from another computer) and gets the QuickBooks response back in the same call, using only the standard library. Your code can run on the QuickBooks machine, on another computer in the office, or anywhere through LedgerCrossing Remote or your own tunnel.

Updated November 11, 2026. QuickBooks is a trademark of Intuit Inc.; LedgerCrossing is not affiliated with or endorsed by Intuit.

Your options for Python and QuickBooks Desktop

Option How Python reaches QuickBooks Where your code runs Good to know
Intuit's COM request processor, through pywin32 win32com.client.Dispatch on Intuit's qbXML request processor Only on the QuickBooks machine Free. Your code opens and closes the connection and session itself, and handles the one-time authorization. 32-bit vs 64-bit mismatches and COM registration errors are commonly reported
A QuickBooks ODBC driver, through pyodbc SQL-style queries Wherever the driver is installed and licensed Familiar if you think in tables. Check how it handles writes and what it costs per machine
The QuickBooks Web Connector Not directly. The Web Connector polls a SOAP web service that you host (in Python, for example) Your web service Built for scheduled sync. Your code can't call it and get an answer back
LedgerCrossing An HTTP POST with an API key (urllib, requests, httpx) The QuickBooks machine, the office network, or anywhere through Remote or a tunnel One-time purchase. Every caller goes through one queue. A validate endpoint checks your qbXML before QuickBooks sees it

How to connect Python to QuickBooks Desktop

1. Install LedgerCrossing on the QuickBooks machine

Run the installer on the Windows computer where QuickBooks Desktop runs, and follow setup. The first time it connects, QuickBooks asks you to allow it: see QuickBooks authorization.

2. Copy the API key

Open Settings > Allowed Connections and copy the API key. You only need it when your code runs on another computer; on the QuickBooks machine itself, no key is needed. Your code sends it in the X-Api-Key header. (Authorization: Bearer <key> works too.)

3. Run your first query

This script lists the first 10 customers. It's based on customer_query.py from our public examples on GitHub (LedgerCrossing/quickbooks-desktop-mcp, examples/http). It needs nothing outside the standard library.

"""List the first 10 customers from QuickBooks Desktop through LedgerCrossing (standard library only)."""
import json
import os
import urllib.error
import urllib.request

LC_URL = os.environ.get("LC_URL", "http://localhost:8284")
LC_KEY = os.environ.get("LC_KEY", "")  # API key from Settings > Allowed Connections (not needed on the QuickBooks machine)

QBXML = (
    '<?xml version="1.0"?><?qbxml version="13.0"?>'
    '<QBXML><QBXMLMsgsRq onError="stopOnError">'
    "<CustomerQueryRq><MaxReturned>10</MaxReturned></CustomerQueryRq>"
    "</QBXMLMsgsRq></QBXML>"
)

req = urllib.request.Request(
    f"{LC_URL}/api/v1/qbxml",
    data=json.dumps({"qbxml": QBXML}).encode(),
    headers={"X-Api-Key": LC_KEY, "Content-Type": "application/json"},
)
try:
    with urllib.request.urlopen(req) as resp:
        result = json.load(resp)
except urllib.error.HTTPError as e:
    result = {"status": e.code, **json.loads(e.read() or b"{}")}

print(result["response"] if result.get("success") else result)

On another computer, set the key first. On the QuickBooks machine itself, any value or none works:

set LC_KEY=paste-your-key-here
python customer_query.py

The response is QuickBooks' own qbXML, with statusCode="0" when the request succeeded.

Which company file. It depends on the QuickBooks File page in LedgerCrossing. With Always use this company file, the example works as is. With Use whatever file is open in QuickBooks, open your company file in QuickBooks first. With The caller names the file in each request, add the file to the body:

data=json.dumps({"qbxml": QBXML, "company_file": r"C:\Users\Public\Documents\Intuit\QuickBooks\Company Files\MyCompany.qbw"}).encode(),

4. Run it from another computer

Turn the response into rows

The response is XML text. Parse it with ElementTree and write a CSV. After the query above:

import csv
import xml.etree.ElementTree as ET

root = ET.fromstring(result["response"])
with open("customers.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["FullName", "Balance"])
    for c in root.iter("CustomerRet"):
        w.writerow([c.findtext("FullName"), c.findtext("Balance")])

Check a request before it reaches QuickBooks

qbXML is strict about element order, and QuickBooks rejects a misordered request with a cryptic status. POST /api/v1/validate checks the request against the catalog of 240 qbXML operations without touching QuickBooks, and names the field that's wrong. It accepts raw XML:

qbxml = (
    '<?xml version="1.0"?><?qbxml version="13.0"?><QBXML><QBXMLMsgsRq onError="stopOnError">'
    "<InvoiceQueryRq><MaxReturned>5</MaxReturned><PaidStatus>NotPaidOnly</PaidStatus></InvoiceQueryRq>"
    "</QBXMLMsgsRq></QBXML>"
)
req = urllib.request.Request(
    f"{LC_URL}/api/v1/validate",
    data=qbxml.encode(),
    headers={"X-Api-Key": LC_KEY, "Content-Type": "application/xml"},
)
with urllib.request.urlopen(req) as resp:
    check = json.load(resp)
print("OK to send" if check["valid"] else check["issues"])

If the request isn't valid, issues says what's wrong and where. When it's valid, send the same request to /api/v1/qbxml exactly as in step 3. That one returns up to five unpaid invoices.

Keep list and transaction queries bounded: MaxReturned, an iterator, or a specific record. Open-ended list and transaction queries are refused by default, before they reach QuickBooks.

Handle errors

urllib raises HTTPError on any status other than 2xx. The body is JSON, except a 403 from the door itself, which has none. Branch on the status; the wording of error is meant for people and can change.

try:
    with urllib.request.urlopen(req) as resp:
        result = json.load(resp)
except urllib.error.HTTPError as e:
    body = json.loads(e.read() or b"{}")
    print(f"HTTP {e.code}: {body.get('error')}")
Status Meaning
400 Malformed request, an open-ended query, or a company file the QuickBooks File page doesn't allow
401 Missing or wrong API key (from another computer; requests on the QuickBooks machine itself need no key)
403 Over the internet (Remote): the person is read-only, or the company file is outside their list. Refused at the door, with no body: a request from a web page on another website, or, from the office network, Local network is off or the caller isn't on a private address or the allowlist
409 Conflicts with QuickBooks' current state, for example the file is in use. code says which
429 Queue full, waited too long, or too many wrong API keys in a minute. Wait for Retry-After and try again
500 A fault inside LedgerCrossing. code says which
503 LedgerCrossing is shutting down (code is SHUTTING_DOWN). Wait for Retry-After (30 seconds) and try again

A request QuickBooks itself rejects still comes back as 200, with the error in the qbXML statusCode. The full list of codes is on the API page.

Prefer requests?

import os
import requests

r = requests.post(
    f"{os.environ.get('LC_URL', 'http://localhost:8284')}/api/v1/qbxml",
    json={"qbxml": QBXML},
    headers={"X-Api-Key": os.environ.get("LC_KEY", "")},
    timeout=120,
)
result = r.json() if r.content else {}
print(result["response"] if result.get("success") else f"HTTP {r.status_code}: {result.get('error')}")

Run it on a schedule

Point Windows Task Scheduler at python.exe C:\path\to\script.py, with LC_KEY set for the account the task runs as. The QuickBooks machine has to be on. If QuickBooks may be closed, name the company file (in the request or on the QuickBooks File page): the first call is then slower while LedgerCrossing opens it. For batches, see QuickBooks Desktop API performance.

What it works with

If you rely on an off-the-shelf app that syncs through the Web Connector, that vendor controls the connection, and LedgerCrossing doesn't slip in underneath it.

Frequently asked questions

Is there a Python SDK for QuickBooks Desktop? Intuit's QuickBooks Desktop SDK is a Windows COM interface, not a Python library. From Python you can call it through pywin32, only on the QuickBooks machine, and manage the connection and session yourself. With LedgerCrossing, Python makes an ordinary HTTP call instead, from that machine or from another one.

Does my Python code have to run on Windows? No. LedgerCrossing runs on the Windows machine with QuickBooks. Your code can run on Linux, macOS or Windows, as long as it can reach that machine's address.

Can my code create invoices and other records? Yes. All 240 qbXML operations are available, including adds, mods and deletes. Over Remote, internet access starts read-only until you allow writes. If someone has the company file open in QuickBooks on that same computer, requests run in their session, as their QuickBooks user.

Why was my query refused with a 400? Open-ended list and transaction queries are refused by default. Add <MaxReturned>, page with an iterator, or name the record you want.

Do I need the QuickBooks Web Connector? No. LedgerCrossing talks to QuickBooks directly through Intuit's connection on that machine, and your code gets the answer in the same call.

Can I use AI agents with the same install? Yes. LedgerCrossing includes an MCP server, so AI agents go through the same queue and access rules as your code: claude.ai, ChatGPT, Meta Muse and Grok Bot over LedgerCrossing Remote, and Claude Desktop, Claude Code, Cursor, VS Code, Codex and Gemini CLI on the QuickBooks computer or office network. See the MCP quickstart.

What does it cost? $299 one-time per machine, with unlimited company files. No subscription for the API. 30-day money-back guarantee.

Related: AI for QuickBooks Desktop · Connect Claude to QuickBooks Desktop · Connect ChatGPT to QuickBooks Desktop

Buy LedgerCrossing — $299 →