Build Against QuickBooks Desktop Without Installing QuickBooks
- quickbooks desktop sandbox
- test quickbooks desktop integration
- quickbooks desktop api without quickbooks

Quick answers
Do I need QuickBooks installed to build a Desktop integration? No — every TenkeyBridge organization gets a free developer sandbox realm that answers the QBO-compatible /v3 API from a hosted emulator, with no QuickBooks, Windows machine, or Web Connector involved.
What's in the sandbox? A seeded fictional company, ExampleCo Landscaping: about 45 customers plus a year of invoices, payments, and bills, with fixed record IDs so tests can hard-code them.
Can I reset it? Yes — on demand to either a sample-data or empty template, plus a nightly reset.
Does the sandbox count against my trial realm limit? No. The developer sandbox is free and separate from your trial realms.
What are the limits? 2,000 REST calls per day and 10,000 records per sandbox realm; unsupported features return 400 SANDBOX_UNSUPPORTED rather than failing silently.
The usual first-week tax on a Desktop integration
If you've shipped against the QuickBooks Online API, your dev loop is boring in the best way: create a sandbox company in the developer portal, run the OAuth dance, start firing requests. Nothing is installed. Nothing is licensed. Nothing has a Windows-only dependency.
The traditional QuickBooks Desktop path is the opposite. Before you write a line of integration code you need a Windows machine (or a VM, or a hosted desktop), a copy of QuickBooks Desktop or Enterprise, a company file, and — if you want anything remotely production-shaped — the Web Connector configured with a QWC file and a password you will forget. Then you discover the file is in single-user mode, or the SDK request you're testing only behaves correctly when the file is open, or your VM snapshot drifted and your tests now depend on invoice #1043 that no longer exists.
That setup cost is not interesting work. It's also the main reason Desktop support tends to get punted to "next quarter" on integration roadmaps.
TenkeyBridge's developer sandbox removes it. You get a realm ID, you get tokens, you get a /v3 API that behaves like a Desktop company file — and none of it runs on your laptop.
What the sandbox actually is
The sandbox is a realm attached to your TenkeyBridge organization that is answered by a hosted emulator instead of a real QuickBooks company file. From the client's perspective there is nothing special about it: same host, same OAuth2, same resource paths, same query language.
GET https://api.tenkeybridge.com/v3/company/{realmId}/query?query=select%20*%20from%20Customer%20maxresults%2010
Authorization: Bearer {access_token}
Accept: application/json
Swap the realm ID and the same request goes to a real Desktop file later. That's the whole point — you write the integration once, against the same shapes you already handle for QuickBooks Online, and the target changes without the code changing. If you're mid-evaluation, the checklist for pointing a QBO integration at Desktop walks through the parts that do need attention.
Key properties, straight from the docs:
- No QuickBooks required. No install, no license, no Windows host, no Web Connector.
- Free. The developer sandbox doesn't consume a trial realm, so evaluating and building don't compete for the same slot.
- Seeded with real-shaped data. ExampleCo Landscaping, described below.
- Resettable. On demand, to a sample-data or empty template, plus a nightly reset.
- Honest about gaps. Anything the emulator doesn't support returns
400 SANDBOX_UNSUPPORTED.
The seeded company: ExampleCo Landscaping
An empty company file is useless for development. You end up writing a fixture script that creates customers, items, and a chart of accounts before you can test the one code path you care about, and that script becomes a second integration you have to maintain.
The sandbox is seeded instead. ExampleCo Landscaping ships with roughly 45 customers and a year's worth of transaction history — invoices, payments, and bills — so paging, date filtering, and aging logic have something to chew on.
The detail that matters most for testing: record IDs are fixed. The same customer has the same Id after every reset. That means a test can do this without a setup phase:
// ExampleCo Landscaping sandbox: IDs are stable across resets
const invoice = await tkb.get(`/v3/company/${realmId}/invoice/${KNOWN_INVOICE_ID}`);
assert.equal(invoice.Invoice.CustomerRef.value, KNOWN_CUSTOMER_ID);
Hard-coded IDs are normally a test smell. Here they're a feature, because the fixture contract is stable by design. If you'd rather start from nothing — for example, to test your own onboarding/backfill path on a brand-new file — reset to the empty template instead.
When your examples need a human, use the house-style ones: ExampleCo Landscaping is owned by Pat Owner. Nothing in the seeded data belongs to a real business.
Getting tokens for the sandbox realm
The OAuth2 flow is the flow you already implement. Register a client, redirect the user to authorize, exchange the code, get an access token and refresh token, and include the realm ID on every request. The sandbox realm appears in the authorization step like any other realm, and its ID goes in the same place in the URL path.
Two pieces of practical advice from the docs:
Use a separate OAuth client for testing. Keep sandbox credentials out of the same client you use for production realms. Separate clients mean separate secrets, separate redirect URIs, and separate revocation blast radius — so rotating a test secret or leaking one in CI logs doesn't touch customers. It also keeps your token storage honest: if your integration can't hold tokens for two clients at once, you'll find out now rather than during your first support escalation.
Don't hard-code the realm ID in application code. Put it in config or environment, the way you already do for QBO sandbox versus production. The whole value of the swap is that only configuration changes.
The mechanics — client registration, the authorize URL, token exchange, refresh — are covered end to end in the quickstart.
Using the sandbox in CI
A hosted sandbox is a genuinely good CI target: no VM to boot, no license to check out, no Windows runner. A few habits keep it reliable.
Reset once per run, not once per test. A reset is a whole-realm operation. Calling it between tests is slow and invites races if anything runs in parallel. Reset at the start of the pipeline, then let the suite work against known state.
Write tests that tolerate their own leftovers. Because you reset per run and not per test, a test that creates an invoice leaves it behind for the rest of that run. Assert on IDs you created or on seeded IDs — not on collection counts like "there are exactly 45 customers," which the first POST /customer in the suite will break.
Always page, always set MAXRESULTS. Queries against a year of transactions return more than you want in one response. Be explicit:
select * from Invoice where TxnDate > '2024-01-01' startposition 1 maxresults 100
Then walk startposition forward until you get a short page. This is not just a CI nicety — it's the same pagination discipline you need against a real Desktop file, where large result sets are slower and the emulator's forgiving response times will flatter you.
Budget your calls. The sandbox allows 2,000 REST calls per day. That's plenty for a focused suite, thin if every push runs an exhaustive matrix of full-table scans on three branches at once. If you're getting close, cut redundant reads before you cut coverage — and consider gating the heaviest suite to main.
Respect the 10,000-record ceiling. A test that creates records in a loop and never resets will eventually hit it. The nightly reset is a backstop, not a strategy.
SANDBOX_UNSUPPORTED is the feature
The emulator does not implement everything a real QuickBooks Desktop file does. Rather than returning a plausible-looking empty result or silently ignoring a field, the sandbox returns an explicit error:
{
"Fault": {
"type": "ValidationFault",
"Error": [{
"code": "SANDBOX_UNSUPPORTED",
"Message": "Not supported in the developer sandbox",
"Detail": "This operation is not emulated. See the compatibility docs."
}]
}
}
A 400 you can see is worth far more than a 200 that lied to you. The failure mode this prevents is the expensive one: you build a feature, your tests pass green for six weeks, and then the first real company file tells you the operation was never going to work that way.
Treat SANDBOX_UNSUPPORTED as a signal to go read the Desktop compatibility matrix and decide whether the gap is in the emulator, in Desktop itself, or in your assumption. Those are three different problems with three different fixes.
What the sandbox is not
Being plain about this is more useful than overselling it.
The sandbox emulates Desktop behavior. It is a faithful model of the API surface and the data shapes, and it's the right place to build. It is not a QuickBooks Desktop installation, and the docs maintain a list of known differences for exactly that reason. In general, the things a hosted emulator can't fully reproduce are the things that come from a real file on a real machine:
- Timing and throughput. A real file goes through a Windows host and the Desktop SDK. Requests that feel instant against the emulator can take meaningfully longer, and long-running writes behave differently under load.
- File and session state. Single-user versus multi-user mode, a file that's open in the UI, a hosted environment where somebody else holds a lock. If your customers run on RDS or a hosted provider, the multi-user and hosted QuickBooks Desktop guide is the relevant reading.
- Version and edition quirks. Desktop Pro, Premier, and Enterprise don't expose identical feature sets, and behavior shifts between year versions.
- Real-world data mess. Customer files carry a decade of custom fields, inactive list entries, odd item types, and names that violate whatever assumption you just made.
- Native error variety. The emulator raises the errors it models. Real files produce a wider range of qbXML status codes and HRESULTs, mapped into QBO-style faults — worth understanding before you hit them in production, and the error-mapping walkthrough covers the translation layer.
When to graduate to a real company file
Build in the sandbox. Verify on a real file. Concretely, move to a real company file when you are:
- Done with happy-path development. Once CRUD, queries, and pagination work, the remaining risk lives in the real environment.
- Testing performance or batch behavior. Sync windows, nightly backfills, and anything with a timeout budget need real latency numbers.
- Exercising concurrency. Locking, multi-user mode, and "the bookkeeper has the file open" are not emulator problems.
- Validating against a specific customer's setup. Their version, their edition, their custom fields, their chart of accounts.
- Preparing for release. A pre-launch pass against at least one real file — ideally one that looks like your median customer — catches the class of bug the sandbox structurally cannot.
That's the trade: the sandbox buys you a fast, free, resettable dev loop and costs you fidelity at the edges. Knowing exactly where those edges are is the point of publishing the differences list rather than hiding it.
FAQ
Do I need QuickBooks installed to build against the API?
No. The developer sandbox is hosted, so you can develop and run tests with no QuickBooks license, no Windows machine, and no Web Connector. You'll want access to a real company file before you ship, but not to start.
What data is in the sandbox?
ExampleCo Landscaping: roughly 45 customers plus about a year of invoices, payments, and bills, with fixed record IDs across resets. You can also reset to an empty template if you need to test a first-run backfill.
Can I use the sandbox in CI?
Yes. Reset once at the start of the pipeline, use a dedicated test OAuth client, and page queries with MAXRESULTS. Keep the 2,000-calls-per-day and 10,000-record limits in mind when you size the suite.
How is it different from a real company file?
It's an emulator, so timing, multi-user and file-lock behavior, edition and version quirks, and real-world data mess aren't fully reproduced. Anything not emulated returns 400 SANDBOX_UNSUPPORTED instead of a misleading success, and the compatibility docs list known differences.
Does the sandbox use the same OAuth2 flow as production?
Yes — same authorization code flow, same token endpoints, same realm ID in the request path. Use a separate OAuth client for testing so test credentials and production credentials rotate independently.
Can more than one developer share a sandbox realm?
It's a single realm per organization, so a shared reset affects everyone using it. In practice, teams point local development at the sandbox and keep CI on its own schedule so a mid-run reset doesn't blow up someone's debugging session. If you need fully isolated state, that's a good reason to look at trial realms against real files. If you want to poke at it before reading anything else: try.tenkeybridge.com, or the quickstart if you'd rather start with tokens in hand.