TypeScript
The official superbooks npm package — typed per-domain methods, error handling, and retries.
The superbooks package wraps the MCP API in typed, per-domain methods, so you
call sb.transactions.list() instead of assembling JSON-RPC envelopes.
Ships both ESM and CommonJS, requires Node.js 20 or newer, and has zero runtime dependencies.
Install
npm install superbooksCreating a client
import { SuperBooks } from "superbooks";
const sb = new SuperBooks({ apiKey: process.env.SUPERBOOKS_API_KEY });apiKey falls back to the SUPERBOOKS_API_KEY environment variable. If neither
is set, the constructor throws a SuperBooksError with code unauthorized — so
you find out at construction, not on the first call.
| Option | Default | Notes |
|---|---|---|
apiKey | SUPERBOOKS_API_KEY | Minted at Settings → Developer. See Authentication. |
baseUrl | https://api.superbooks.io | A URL already ending in /mcp is accepted as-is. |
timeoutMs | 60000 | Per request. Pass 0 to disable. |
maxRetries | 2 | See Retries. |
maxRetryDelaySeconds | 60 | Caps how long a Retry-After may park a call. |
headers | — | Merged into every request. Cannot override Authorization. |
fetch | global fetch | Injection point for proxies and tests. |
clientInfo | — | { name, version } advertised during MCP initialize. |
throwOnToolError | true | See Error handling. |
Note the exact names: timeoutMs, not timeout; maxRetries, not
maxAttempts.
Calling tools
Each MCP tool maps to a method on its domain — strip the domain prefix, camelCase the rest:
const { data } = await sb.transactions.list({
from: "2026-01-01",
to: "2026-03-31",
limit: 50,
});
await sb.invoices.createDraft({
customer_id: "00000000-0000-0000-0000-000000000000",
currency: "USD",
issue_date: "2026-04-01",
due_date: "2026-04-30",
line_items: [{ name: "Consulting", quantity: 10, price: 150 }],
});Three worth knowing because they read a little oddly: bank_accounts_list is
sb.bankAccounts.list(), search_global is sb.search.global(), and delete
and void are legal method names — sb.transactions.delete() and
sb.invoices.void() are real.
Every tool, with its full parameter list, is in the API reference.
What a call returns
A ToolResult, not plain parsed JSON:
interface ToolResult {
data: unknown;
content: ContentBlock[];
isError: boolean;
}data is the parsed payload, so destructuring is the idiomatic form:
const { data } = await sb.customers.list({ limit: 5 });Argument types are exported; result types are not. Every tool has a
PascalCase(toolName) + "Params" type — TransactionsListParams,
InvoicesCreateDraftParams, BankAccountsListParams.
The asymmetry is deliberate: input schemas are a published contract served by
tools/list, but result shapes are not part of that contract, so data is
unknown rather than a promise the server never made. Narrow it yourself:
const { data } = await sb.customers.list({ limit: 5 });
const { items } = data as { items: Array<{ id: string; name: string }> };Pagination
Manual — there is no auto-paginating iterator.
The field names are asymmetric: you pass cursor in and read
nextCursor out. nextCursor is null on the last page.
let cursor: string | undefined;
do {
const { data } = await sb.transactions.list({ cursor, limit: 100 });
const page = data as { items: unknown[]; nextCursor: string | null };
for (const transaction of page.items) {
// ...
}
cursor = page.nextCursor ?? undefined;
} while (cursor);limit accepts 1–100 on every paginated tool, but the default varies by
tool — 25 for transactions.list, invoices.list, customers.list and
tracker.listProjects, 50 for tracker.listEntries, 30 for search.global,
and 10 for reports.topCustomers. Pass it explicitly rather than relying on the
default. Prefer large pages: one call for 100 rows costs a hundredth of the
rate-limit budget that a hundred single-row calls would.
Error handling
Everything extends SuperBooksError, which carries status?: number,
rpcCode?: number, and a code — one of unauthorized, forbidden,
rate_limited, bad_request, server_error, connection_error,
protocol_error, or tool_error. A single instanceof therefore catches the
whole surface:
import {
SuperBooks,
SuperBooksError,
SuperBooksRateLimitError,
} from "superbooks";
try {
const { data } = await sb.transactions.list({ limit: 10 });
} catch (error) {
if (error instanceof SuperBooksRateLimitError) {
console.error(`Rate limited; retry in ${error.retryAfterSeconds}s`);
} else if (error instanceof SuperBooksError) {
console.error(error.code, error.status, error.message);
}
throw error;
}| Condition | Class | Extra fields |
|---|---|---|
| 401 | SuperBooksAuthError | — |
| 403 | SuperBooksPermissionError | — |
| 429 | SuperBooksRateLimitError | retryAfterSeconds? |
| Network, timeout, abort | SuperBooksConnectionError | — |
| Malformed or non-JSON-RPC reply | SuperBooksProtocolError | — |
| Tool ran and refused | SuperBooksToolError | toolName, rpcCode, data |
A JSON-RPC error on a tool call raises SuperBooksToolError with both
toolName and rpcCode populated.
There is no requestId field. The API returns no correlation id, so do not
build logging or support workflows around one.
SuperBooksToolError is thrown by default when a tool reports isError. Pass
throwOnToolError: false to get the ToolResult back with isError: true
instead — the better shape when refusals are ordinary control flow for you
rather than exceptions.
Retries
On by default, but narrower than you might assume.
Only 429 is retried. Two retries by default, so three requests worst case.
Retry-After is honoured and capped at maxRetryDelaySeconds (60); if the
header is missing or unparseable, the delay falls back to 2^attempt seconds.
5xx is deliberately not retried. A 429 was rejected before the tool ran,
so replaying it is safe. A 500 from invoices_send may mean the email already
went out — and tool calls are not idempotent, so replaying a write is the
caller's decision, not the client's.
Using SuperBooks from an AI client
The SDK is for writing code. If you want Claude, Cursor, or another AI client to work with your SuperBooks data, you do not need this package at all — connect the client to the hosted endpoint directly:
claude mcp add --transport http superbooks https://api.superbooks.io/mcp \
--header "Authorization: Bearer sb_your_api_key_here"See Connecting AI clients for per-client setup.