Skip to main content

qbwc-kit

CI PyPI Python License

QuickBooks Desktop has no HTTP API. The only supported way in is the Web Connector: a Windows service that polls your SOAP endpoint on a schedule, asks it for qbXML, hands that to QuickBooks over COM, and posts the response back.

Before you can read a single invoice you have to implement eight SOAP callbacks, serve a WSDL, keep a ticket-based session alive across many HTTP round trips, and hand-build XML that QuickBooks rejects with unhelpful errors when the element order is wrong. I wrote all of that, so the only part left is the one specific to your data.

Why QuickBooks Desktop has no API at all, and what the Web Connector actually costs you: docs/why-web-connector.md.

from qbwc_kit import QBWCService, StaticAuthenticator, qbxml
from qbwc_kit.qbxml import QBXMLRequest
from qbwc_kit.server import create_app


class SyncCustomers:
    name = "customers"

    def run(self, ctx):
        request = qbxml.query("Customer", max_returned=100, iterator="Start")
        while True:
            result = yield QBXMLRequest([request])
            page = result.first().raise_for_status()
            save(page.records)
            if not page.has_more:
                return
            request.iterator = "Continue"
            request.iterator_id = page.iterator_id


service = QBWCService(
    authenticator=StaticAuthenticator("qbwc", "s3cret", [SyncCustomers()])
)
app = create_app(service, endpoint_url="https://books.example.com/qbwc")

The while loop is why I wrote this. Each yield suspends the task until the Web Connector comes back with the response, so pagination, conditional writes, and read-then-write jobs stay in ordinary control flow instead of being flattened into a per-request state machine.

Install

It is on PyPI:

pip install qbwc-kit                    # core: standard library only
pip install 'qbwc-kit[server]'          # adds the FastAPI adapter

Or clone it and pip install -e '.[dev]' to run the tests.

The core (SOAP, qbXML, sessions, the WSDL generator) has no dependencies outside the standard library. FastAPI is only needed if you use qbwc_kit.server. The service itself is just dispatch(soap_body) -> soap_body, so it drops into Flask, Django or bare WSGI unchanged.

Testing without QuickBooks

Exercising a Web Connector integration normally requires a Windows box, a QuickBooks Desktop install, an open company file, and a human clicking Update Selected. The failure modes that matter (a task that never terminates, an iterator that loops forever, a non-zero status nobody checked) are the ones I kept only finding in production.

qbwc_kit.testing replaces both ends:

from qbwc_kit.testing import FakeQuickBooks, FakeWebConnector, service_transport

connector = FakeWebConnector(transport=service_transport(service),
                             username="qbwc", password="s3cret")
quickbooks = FakeQuickBooks(entities={"Customer": [{"ListID": "1", "Name": "Acme"}]})

result = connector.run_update(quickbooks)

assert result.progress[-1] == 100
assert quickbooks.seen[0].count("iterator=\"Start\"") == 1

My FakeWebConnector replays the real call sequence (clientVersion, authenticate, the sendRequestXML/receiveResponseXML loop, closeConnection) and raises if the session doesn't terminate, which catches runaway iterators in milliseconds instead of in the connector's log. FakeQuickBooks answers qbXML the way QuickBooks does: paged iterators, MaxReturned, status 1 for an empty result, status 3100 for an unsupported request.

Things that caught me out

Status codes ride on successful envelopes. A request QuickBooks refused (status 3100, "not available in this edition") comes back as a perfectly well-formed response document with no records in it. If you parse for rows and ignore the status, an unsupported request is indistinguishable from an empty table, and a cache built on top of it degrades into empty results without anything logging an error. Every Response here carries its status, ok distinguishes "nothing found" (status 1, genuinely fine) from "never ran", and raise_for_status() is one call away.

Element order is part of the schema. qbXML is a sequence, not a bag. MaxReturned before the filters, EditSequence before the fields being changed. The builders emit the right order, so you pass a dict and do not have to track it.

Writes use optimistic concurrency. Every Mod request must carry the EditSequence from the last read, and a stale one is rejected rather than silently clobbering another user's edit. qbxml.mod() requires it as a keyword argument for that reason.

Iterators only exist on some entities. Asking for one on an entity that doesn't support it is an opaque parse error from QuickBooks, so I raise at build time instead.

Returning 100 ends the session. receiveResponseXML returns percent complete, and a progress calculation that rounds up too early silently truncates the sync. Session.progress() caps at 99 until the work is genuinely done. 100 is reserved for a session that ran out of tasks with nothing retired; a task that raises is retired and the session continues with the next task, with the error available from getLastError; a session that ended through a retirement reports 99, never 100.

A failing task retires itself and the session continues. One broken job must not cancel the queue behind it; -1 from receiveResponseXML is reserved for QuickBooks itself failing.

An unknown ticket is normal. Restart the server mid-update and the next callback arrives with a ticket that no longer exists. Faulting makes the Web Connector retry forever, so I tell it the session is over instead.

Layout

Module What it does
qbwc_kit.soap The small SOAP 1.1 slice QBWC actually uses: parse a call, build a response or fault
qbwc_kit.qbxml Request builders (query, add, mod) and a status-aware response parser
qbwc_kit.session Generator-based tasks, the request/response loop, ticket store with TTL
qbwc_kit.service The eight callbacks, framework-agnostic
qbwc_kit.wsdl WSDL generation, plus the .qwc file users import into the connector
qbwc_kit.server Optional FastAPI adapter
qbwc_kit.testing FakeWebConnector, FakeQuickBooks

Example

examples/sync_to_sqlite.py is a complete integration: it mirrors customers and invoices into SQLite, syncs incrementally off a stored watermark, and generates the .qwc file to import into the Web Connector.

python examples/sync_to_sqlite.py --write-qwc mirror.qwc   # generate the connector file
python examples/sync_to_sqlite.py --url https://books.example.com/qbwc

Two details in it are worth copying. The watermark only advances after every page has been written, so an interrupted run repeats work instead of skipping it. And it is rewound by a minute, because QuickBooks stamps TimeModified from the workstation clock, so a record saved during the sync can otherwise land just behind the watermark and never be picked up again.

The example is covered by the test suite, so it can't drift from the library.

Deployment notes

  • QBWC will not accept an endpoint on plain HTTP unless it is localhost. Use TLS.
  • soap:address in the WSDL has to be the URL the connector actually posts to. Behind a reverse proxy, that is the public URL, not http://localhost:8000.
  • OwnerID and FileID in the .qwc file identify your integration to QuickBooks. Generate them once and keep them; changing them forces every user to re-authorise.
  • The connector authenticates with a password stored in Windows' credential store. Treat the authenticate callback as a real auth boundary. StaticAuthenticator compares with secrets.compare_digest, and anything you write should too.

Scope

Read and write access to list and transaction entities through qbXML, which is what the Web Connector exposes. Not covered: QuickBooks Online (which has a REST API of its own), qbposXML for Point of Sale, and the direct COM QBFC interface, which needs code running on the same Windows machine as QuickBooks.

Development

pip install -e '.[dev]'
pytest -q
ruff check . && ruff format --check .

License

MIT

Metadata

Release files for qbwc-kit 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qbwc-kit 0.1.3
File Size Uploaded
qbwc_kit-0.1.3.tar.gz 50.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qbwc-kit 0.1.3
File Interpreter ABI Platform
qbwc_kit-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 87.1 kB

Release files / qbwc_kit-0.1.3.tar.gz

Download URL qbwc_kit-0.1.3.tar.gz
Size 50.6 kB
Tags Source
SHA-256 checksum
How to use checksums
59e99c9a37c0ebe61cde33c0c48c4bae8275cefb496505e78765964391c975ac
BLAKE2b-256 checksum
How to use checksums
3bd9bf5a60835b7162735331e175d4b36d6223365fadad9d5b61dc7de380a9f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / qbwc_kit-0.1.3-py3-none-any.whl

Download URL qbwc_kit-0.1.3-py3-none-any.whl
Size 36.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6c52b5f9bc840acd4c6fceefc6f045ae809efa82bf1af13c84189d261b1c043
BLAKE2b-256 checksum
How to use checksums
a244aed7ebf7cfe627fef9da93cc0378b652f76cb88474e8af7342260a785361
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page