LedgerCrossingLedgerCrossing
HomePricingRemoteFAQ

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> or Authorization: Bearer <key>. A key is generated on first run. A missing or wrong key gets 401.
  • 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 403 until 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 429 with Retry-After: 60 for 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>:8284 with the API key.
  • Anywhere else (cloud functions, a hosted app, a laptop on the road), through either:

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.