An HTTP API for QuickBooks Desktop
QuickBooks Desktop has no cloud API. The only way in is qbXML through a COM interface on the Windows machine where QuickBooks runs. LedgerCrossing runs on that machine and gives it one HTTP endpoint: POST a qbXML request, get the QuickBooks response back in the same call. Any language that can make a web request can use it.
$299 one-time per machine. Unlimited company files. 30-day money-back guarantee.
Works with anything that can make a web request. 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.
One request, one response
The request body is JSON with your qbXML in the qbxml field:
request.json
{
"qbxml": "<?xml version=\"1.0\"?><?qbxml version=\"13.0\"?><QBXML><QBXMLMsgsRq onError=\"stopOnError\"><CustomerQueryRq><MaxReturned>2</MaxReturned><IncludeRetElement>FullName</IncludeRetElement><IncludeRetElement>Balance</IncludeRetElement></CustomerQueryRq></QBXMLMsgsRq></QBXML>"
}
This example uses setup's "Always use this company file". With the default setting ("The caller
names the file in each request"), add
"company_file": "C:\\Users\\Public\\Documents\\Intuit\\QuickBooks\\Company Files\\MyCompany.qbw"
to the JSON.
curl http://qb-server:8284/api/v1/qbxml \
-H "X-Api-Key: $LEDGERCROSSING_KEY" \
-H "Content-Type: application/json" \
--data @request.json
The response arrives in the same call, 200 OK:
{"success":true,"response":"<?xml version=\"1.0\" ?>\n<QBXML>\n<QBXMLMsgsRs>\n<CustomerQueryRs statusCode=\"0\" statusSeverity=\"Info\" statusMessage=\"Status OK\">\n<CustomerRet>\n<FullName>Abercrombie, Kristy</FullName>\n<Balance>0.00</Balance>\n</CustomerRet>\n<CustomerRet>\n<FullName>Abercrombie, Kristy:Family Room</FullName>\n<Balance>0.00</Balance>\n</CustomerRet>\n</CustomerQueryRs>\n</QBXMLMsgsRs>\n</QBXML>\n"}
That's a real response from Intuit's sample company file. With QuickBooks closed, the first call
took 7.2 s while LedgerCrossing opened the file. Once the file was open, the same request took
0.08 s. POST /api/v1/session/open does that warm-up before a batch.
From the QuickBooks machine itself (localhost) the key is optional. Every other caller needs it.
The same request in Python:
import requests
QBXML = """<?xml version="1.0"?><?qbxml version="13.0"?>
<QBXML><QBXMLMsgsRq onError="stopOnError">
<CustomerQueryRq>
<MaxReturned>2</MaxReturned>
<IncludeRetElement>FullName</IncludeRetElement>
<IncludeRetElement>Balance</IncludeRetElement>
</CustomerQueryRq>
</QBXMLMsgsRq></QBXML>"""
r = requests.post(
"http://qb-server:8284/api/v1/qbxml",
headers={"X-Api-Key": KEY},
json={"qbxml": QBXML},
timeout=90, # a request can wait up to 60 s in the queue
)
r.raise_for_status()
print(r.json()["response"]) # the qbXML response from QuickBooks
Which company file. By default, each request names its file in company_file, for example
"company_file": "\\\\server\\share\\Company.qbw". LedgerCrossing opens it, and switches between
files as requests arrive. Or pick one file in Settings ("Always use this company file"), or "Use
whatever file is open in QuickBooks", and leave the field out. In those two modes LedgerCrossing
doesn't switch files: a request naming a different file is refused.
The endpoints
| Endpoint | What it does |
|---|---|
POST /api/v1/qbxml |
Run a qbXML request in QuickBooks and return the response |
POST /api/v1/validate |
Check a request against the operation catalog without touching QuickBooks |
GET /api/v1/docs?search= |
Search the catalog of 240 qbXML operations |
GET /api/v1/docs/{operation} |
Field reference for one operation: element order, required fields, max lengths, enums, minimum qbXML version |
GET /api/v1/errors/{code} |
Explain a QuickBooks status code in plain words |
POST /api/v1/session/open · /close |
Warm the QuickBooks session before a batch, release it after |
GET /api/v1/health · /diagnostics |
Liveness with no key (details show only on this computer); version, open company file, queue depth (diagnostics: key required) |
POST /mcp |
The same capabilities as an MCP server for AI agents |
Validation is the one developers use most. Element order in qbXML is strict, and QuickBooks rejects
a request with a slow, cryptic status. /api/v1/validate names the field that's wrong before
QuickBooks ever sees it. Here is the example query with <MaxReturned> moved after the
<IncludeRetElement> lines; the answer came back in 3 ms:
{"success":true,"valid":false,"issues":[{"severity":"error","message":"<MaxReturned> is out of order: it must appear before <IncludeRetElement> inside <CustomerQueryRq>. QuickBooks enforces exact element order.","path":"QBXMLMsgsRq/CustomerQueryRq/MaxReturned"}]}
Auth and access
- API key: send it as
X-Api-Key: <key>orAuthorization: Bearer <key>. A key is generated on first run. A missing or wrong key gets401. - This computer is always allowed.
- Office network: off until you turn it on. It accepts only private office and VPN addresses, and you can restrict it to specific addresses or CIDR ranges. The Windows network on the QuickBooks machine must be set to Private or Domain, not Public: on a Public network, other machines can't connect.
- From outside the office: each person signs in with a username and password you create, through
LedgerCrossing Remote. Internet access starts read-only: writes are refused with
403until you allow them. - HTTPS: turn it on in Settings. LedgerCrossing creates a certificate for the machine and shows its SHA-256 fingerprint for your client to pin.
- Repeated wrong keys: after 10 missing or wrong keys from one address, that address gets
429withRetry-After: 60for a minute. - Audit log: every qbXML request (source address, operations, company file) and every failed or blocked key attempt is written to it.
The queue, and what the status codes mean
QuickBooks Desktop handles one request at a time, with one company file open. LedgerCrossing puts every caller (your integrations, AI agents, several people) through one queue, so calls wait their turn instead of colliding.
| Status | Meaning | Body |
|---|---|---|
200 |
QuickBooks ran the request | {"success": true, "response": "<qbXML>"} |
400 |
Malformed request, or an unbounded query (see below) | {"success": false, "error": "..."} |
401 |
Missing or wrong API key | {"success": false, "error": "..."} + WWW-Authenticate: Bearer |
403 |
Blocked by access rules (read-only or company-file scope) | {"success": false, "error": "..."} |
409 |
Conflicts with QuickBooks' current state, e.g. the file is in use | {"success": false, "error": "...", "code": "..."} |
429 |
Queue full, or the request waited too long | {"success": false, "error": "...", "queue_depth": n} + Retry-After |
429 |
10 missing or wrong keys from one address: blocked for a minute | {"success": false, "error": "..."} + Retry-After: 60 |
500 |
A genuine fault | {"success": false, "error": "...", "code": "..."} |
Branch on the status, and on code when the body has one. The wording of error is for people
and can change. Responses also carry Queue-Wait-Ms (how long the request waited), and
File-Contention: true when another caller was holding a different company file.
Unbounded queries are refused by default. A list or transaction query with no MaxReturned, no
iterator and no specific record named can become a long QuickBooks call that can't be cancelled, so
LedgerCrossing rejects it with 400 before it reaches QuickBooks. The example query without
<MaxReturned> gets this back:
{"success":false,"error":"Unbounded query rejected: CustomerQueryRq has no result bound, add <MaxReturned>N</MaxReturned> to cap the result count (preferred); or name the specific records you want with <ListID> / <FullName>, which is already bounded and must NOT be combined with MaxReturned; or, only if you intend to walk the entire result set, page with iterator=\"Start\" and finish it, page until iteratorRemainingCount is 0, or send iterator=\"Stop\" if you stop early, because an abandoned iterator holds the company file open. Unbounded queries on a large company file lock QuickBooks in a call that cannot be canceled. (This guardrail is the RequireBoundedQueries setting.)"}
You can turn this off (RequireBoundedQueries).
How requests are scheduled across company files, and the batching patterns that keep them fast: QuickBooks Desktop API performance.
Where your code can run
- On the QuickBooks machine: call
http://localhost:8284. - On the office network: call
http://<machine>:8284with the API key. - Anywhere else (cloud functions, a hosted app, a laptop on the road), through either:
- LedgerCrossing Remote: a monthly add-on that gives the machine an HTTPS address with per-person sign-in, or
- your own Cloudflare Tunnel: free, set up on your account.
What it works with
- QuickBooks Desktop Pro, Premier and Enterprise, 2019 and later
- Windows 10 and 11, and Windows Server 2016–2025
- All 240 qbXML operations: queries, adds, mods, deletes, reports
- Unlimited company files on one license
AI agents, from the same install
LedgerCrossing includes an MCP server at /mcp, so Claude, Cursor, VS Code and other agents can
work with QuickBooks Desktop through the same queue and the same access rules.
claude.ai and ChatGPT connect through LedgerCrossing Remote.
MCP quickstart
Pricing
$299 one-time per machine.
- Unlimited company files.
- No subscription for the API.
- 30-day money-back guarantee.
- LedgerCrossing Remote is an optional monthly add-on for access from outside the office.
Works with anything that can make a web request. 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.