Appearance
Node.js client
If you already have a QuickBooks Online integration, you don't need a client library — swap the base URL and your existing stack keeps working. For new Node.js code, @tenkeybridge/client is the supported client: a thin, zero-dependency, typed wrapper over the REST surface with OAuth token management built in.
sh
npm install @tenkeybridge/clientRequires Node 18.17+. Ships ESM and CJS, no runtime dependencies.
Connect and read
ts
import { TenkeyBridgeClient } from "@tenkeybridge/client";
const client = new TenkeyBridgeClient({
realmId: process.env.TKB_REALM_ID!,
auth: {
clientId: process.env.TKB_CLIENT_ID!,
clientSecret: process.env.TKB_CLIENT_SECRET!,
refreshToken: process.env.TKB_REFRESH_TOKEN!,
onTokensRefreshed: async (tokens) => saveTokens(tokens),
},
});
const { entities } = await client.customer.query({ maxResults: 10 });
const invoice = await client.invoice.get("2FBE8-1071508936");The auth block accepts a static { accessToken }, the client-credentials + refresh-token shape above, or a TokenManager you construct yourself. With a refresh token, the client refreshes ahead of expiry, single-flights concurrent refreshes, and calls onTokensRefreshed whenever the pair rotates — persist it there. Rotation is soft: the previous refresh token keeps working for a short grace window, and the manager uses that to recover from concurrent-refresh races automatically.
Writes
Same JSON as QuickBooks Online. Updates are always sparse — send Id plus the fields you're changing:
ts
const created = await client.invoice.create({
CustomerRef: { value: "80000001-1234" },
Line: [{
DetailType: "SalesItemLineDetail",
Amount: 100,
SalesItemLineDetail: { ItemRef: { value: "42" }, Qty: 2 },
}],
});
await client.invoice.update({ Id: created.Id!, DocNumber: "INV-1042" });
await client.invoice.delete({ Id: created.Id! });Typed against the matrix
The per-entity helpers and TypeScript types are generated from the same compatibility catalog that gates the gateway, so the client can't promise more than the API delivers:
- Only the 31 live entities get accessors (
client.customer,client.salesReceipt, …). - Read-only entities (Item, Employee, Preferences, TaxRate, …) expose
get/querybut nocreate/update/delete— it's a compile error, not a runtime 400. - Entity types carry only the fields the matrix marks supported; anything Desktop can't honor is absent, so TypeScript flags it before the API has to.
Field-level notes — the exact fix for anything marked partial — live in the entity matrix.
Queries and pagination
ts
const page = await client.query("Invoice", {
where: "TxnDate >= '2026-01-01'",
maxResults: 100,
startPosition: 1,
});
// Auto-pagination
for await (const c of client.customer.queryAll()) {
console.log(c.DisplayName);
}
// Raw QBO query strings work too
await client.rawQuery("select * from Customer maxresults 5");The query language is the documented QBO subset — see IDs, SyncToken & sparse updates for what's accepted.
Errors
API failures throw TenkeyBridgeApiError carrying the QBO Fault plus TenkeyBridge's tkb hint block — stable error code, causes, fixes, and a docs link:
ts
try {
await client.invoice.create(inv);
} catch (err) {
if (err instanceof TenkeyBridgeApiError) {
console.error(err.code, err.tkb?.fixes, err.tkb?.docsUrl);
}
}OAuth failures (expired refresh token, bad credentials) throw TenkeyBridgeOAuthError with the OAuth error code (invalid_grant, …).
Where to go next
- Authentication — getting credentials and the OAuth flow.
- Entity matrix — what's live, field by field.
- Error codes — every stable code and its fix.