@tailor-integration/quickbooks
v0.1.1
Published
QuickBooks Online client for Tailor applications
Readme
@tailor-integration/quickbooks
Typed QuickBooks Online client for Tailor applications.
Runtime constraints
This SDK is only consumable from within Tailor function execution:
- Token retrieval. Integration tokens (
tqb_...) are stored in Tailor's Secret Manager and read at runtime viasecrets.get()— a built-in interface only available inside the Tailor function runtime. - Network reachability. Requests are only accepted from within
the Tailor function runtime. A
tqb_exfiltrated to any non-Tailor environment cannot be redeemed.
The SDK does not enforce this — it'll happily try to fetch from anywhere. The enforcement is environmental.
Install
pnpm add @tailor-integration/quickbooksUsage
// In tailor.config.ts:
import { defineSecretManager } from "@tailor-platform/sdk";
export const secrets = defineSecretManager({
"my-app": {
"qbo-integration-token": process.env.QBO_INTEGRATION_TOKEN ?? "",
},
});
// In a resolver:
import { createResolver, t } from "@tailor-platform/sdk";
import { QuickBooksClient } from "@tailor-integration/quickbooks";
import { secrets } from "../tailor.config";
export default createResolver({
name: "listInvoices",
operation: "query",
output: t.object(
{ Id: t.string(), TotalAmt: t.float() },
{ array: true },
),
body: async () => {
const integrationToken = await secrets.get(
"my-app",
"qbo-integration-token",
);
const client = new QuickBooksClient({ integrationToken });
const { items } = await client.invoice.queryAll(
"SELECT * FROM Invoice WHERE TotalAmt > '100'"
);
return items.map((i) => ({ Id: i.Id, TotalAmt: i.TotalAmt ?? 0 }));
},
});API surface
new QuickBooksClient({
integrationToken: string, // required, must start with "tqb_"
proxyUrl?: string, // defaults to https://qbo-proxy.tailor.studio/proxy
minorVersion?: number, // applied to every call
fetch?: FetchLike, // defaults to globalThis.fetch
maxRetries?: number, // rate-limit retry cap (default 5)
})Resources (~38 total):
| Property | Wire entity | Property | Wire entity |
| --- | --- | --- | --- |
| account | Account | paymentMethod | PaymentMethod |
| attachable | Attachable | preferences | Preferences (RO) |
| bill | Bill | purchase | Purchase |
| billPayment | BillPayment | purchaseOrder | PurchaseOrder |
| budget | Budget (RO) | recurringTransaction | RecurringTransaction (RO) |
| class | Class | refundReceipt | RefundReceipt |
| companyCurrency | CompanyCurrency | salesReceipt | SalesReceipt |
| companyInfo | CompanyInfo (RO) | standardReports | (Reports endpoint) |
| creditMemo | CreditMemo | taxAgency | TaxAgency |
| customer | Customer | taxClassification | TaxClassification |
| customerType | CustomerType | taxCode | TaxCode |
| department | Department | taxRate | TaxRate |
| deposit | Deposit | taxService | TaxService (create-only) |
| employee | Employee | term | Term |
| estimate | Estimate | timeActivity | TimeActivity |
| exchangeRate | ExchangeRate (RO) | transfer | Transfer |
| invoice | Invoice | vendor | Vendor |
| item | Item | vendorCredit | VendorCredit |
| journalEntry | JournalEntry | | |
| payment | Payment | | |
Each non-special resource exposes:
client.<resource>.get(id, opts?)
client.<resource>.query<R>(qstr, opts?) // <R> override for column projection
client.<resource>.queryAll<R>(qstr, opts?) // auto-paginated
client.<resource>.create(data, opts?)
client.<resource>.update(data, opts?)
client.<resource>.delete(id, syncToken, opts?)Top-level:
client.batch(requests, opts?)
client.standardReports.run(reportName, params, opts?)
client.standardReports.getAgedReceivableDetail(params)
client.standardReports.getCustomerBalance(params)
client.taxService.create(input, opts?)
client.request<T>({ method, path, body?, queryParams? }) // escape hatchErrors
import { QuickBooksClientError } from "@tailor-integration/quickbooks";
try {
await client.invoice.get("999");
} catch (err) {
if (err instanceof QuickBooksClientError) {
err.kind; // "intuit" | "proxy" | "network" | "config"
err.code; // proxy: e.g. "SESSION_EXPIRED"; intuit: Fault.Error[0].code
err.status; // HTTP status (gateway) or inner Intuit status
err.fault; // Intuit Fault payload (intuit only)
err.detail; // shorthand for fault.Error[0].Detail
err.element; // shorthand for fault.Error[0].element
err.isReconnectRequired(); // proxy + SESSION_EXPIRED / SESSION_NOT_CONNECTED
err.isRateLimited(); // 429 / RATE_LIMIT_EXCEEDED
err.isPermissionDenied(); // proxy + PERMISSION_DENIED / GRANT_NOT_ACTIVE
err.isIntuitFault(); // kind === "intuit"
}
}v0.1.0 limitations
A few omakase methods were deferred to a later release:
Invoice.getPdf()andCreditMemo.getPdf()— PDF download.Attachable.upload()— multipart upload.Invoice.get()withincludequery param — easy to reintroduce via aGetOptions.extraQueryParamsfield; deferred for v0.2.
RecurringTransaction is exposed as read-only for v0.1.0; its non-standard
mutation surface (/recurringtransaction with custom shapes) will land in
a later release.
For paths the typed resources don't cover, use client.request().
Examples
See apps/qbo-consumer-sandbox/src/resolvers/listCustomers.ts and
apps/qbo-consumer-sandbox/src/executors/receiveDelivery.ts for canonical
usage patterns.
