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
- Another computer in the office: turn on Settings > Allowed Connections > Local network, then set
LC_URL=http://<machine-name>:8284. The Windows network on the QuickBooks machine must be set to Private or Domain, not Public. - Anywhere else (a server in the cloud, a laptop on the road): use LedgerCrossing Remote, a monthly add-on that gives the machine an HTTPS address, or your own Cloudflare Tunnel. Over Remote, code using the API key needs Allow the API key turned on (Settings > Allowed Connections > Internet); it's off by default. Internet access also starts read-only, so writes are refused until you allow them.
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
- QuickBooks Desktop Pro, Premier and Enterprise. Supports QuickBooks Desktop 2019 and later. Older versions back to 2014 use the same Intuit connection and should work, though we haven't tested them; the 30-day guarantee covers you.
- Windows 10 and 11, and Windows Server 2016–2025, on the QuickBooks machine. Your Python code can run on any OS that reaches it.
- All 240 qbXML operations: queries, adds, mods, deletes and reports.
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