Installation
npm install @smartbills/sdk
yarn add @smartbills/sdk
Quick Start
import { SmartbillsClient } from "@smartbills/sdk";
const client = new SmartbillsClient({
accessToken: "your-access-token",
businessId: 42,
locale: "en-CA",
});
const { data, pagination } = await client.expenses.listBusiness({ limit: 25 });
Configuration
Client Options
interface SmartbillsClientOptions {
baseUrl?: string;
accessToken?: string;
businessId?: number;
locale?: string;
timeout?: number;
maxRetries?: number;
retryDelay?: number;
}
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | string | https://api.smartbills.io | API base URL |
accessToken | string | — | OAuth2 bearer token |
businessId | number | — | Default business context for requests |
locale | string | — | Locale for localized responses (en-CA, fr-CA) |
timeout | number | — | Request timeout in milliseconds |
maxRetries | number | — | Maximum automatic retries on transient failures |
retryDelay | number | — | Delay between retries in milliseconds |
Runtime Configuration
const client = new SmartbillsClient();
client.setAccessToken("new-token");
client.setBusinessId(42);
client.setLocale("fr-CA");
console.log(client.accessToken);
console.log(client.businessId);
console.log(client.locale);
Services
Every service is accessed as a property onSmartbillsClient. All methods accept an optional RequestOptions parameter to override businessId, locale, or pass an AbortSignal.
interface RequestOptions {
businessId?: number;
locale?: string;
signal?: AbortSignal;
}
Expenses
const expenses = client.expenses;
const list = await expenses.listBusiness({ limit: 25, page: 1 });
const myExpenses = await expenses.listMy({ limit: 10 });
const employeeExpenses = await expenses.listEmployee(employeeId, { limit: 10 });
const expense = await expenses.getById(expenseId);
await expenses.update(expenseId, {
categoryId: 456,
note: "Office supplies",
});
await expenses.delete(expenseId);
await expenses.updateCategory(expenseId, { categoryId: 789 });
await expenses.updateNote(expenseId, { note: "Updated note" });
await expenses.updateReport(expenseId, { expenseReportId: 10 });
await expenses.updateReview(expenseId, { reviewed: true });
Upload Expenses
const formData = new FormData();
formData.append("files", file);
const uploaded = await expenses.uploadBusiness(formData);
const myUploaded = await expenses.uploadMy(formData);
const employeeUploaded = await expenses.uploadEmployee(employeeId, formData);
Presigned Upload (S3)
const { url, fields } = await expenses.presignUpload({
fileName: "receipt.jpg",
contentType: "image/jpeg",
});
await uploadFileToS3(url, fields, file);
await expenses.confirmUpload({ key: fields.key });
Bulk Operations
await expenses.bulkAssignCategory({ expenseIds: [1, 2, 3], categoryId: 10 });
await expenses.bulkAssignPayerType({ expenseIds: [1, 2], payerType: "BUSINESS" });
await expenses.bulkAssignReport({ expenseIds: [1, 2, 3], expenseReportId: 5 });
await expenses.bulkAssignVendor({ expenseIds: [1, 2], vendorId: 20 });
await expenses.bulkSetNote({ expenseIds: [1, 2], note: "Q1 expenses" });
await expenses.bulkDelete({ expenseIds: [1, 2, 3] });
Export & Download
const blob = await expenses.export({ expenseIds: [1, 2, 3] });
const { blob: file, filename } = await expenses.exportWithFilename({
expenseIds: [1, 2, 3],
});
const attachments = await expenses.downloadAttachments({ expenseIds: [1, 2] });
const single = await expenses.downloadSingleAttachment(expenseId);
Split
const splits = await expenses.split(expenseId, {
splits: [
{ amount: 50, categoryId: 1 },
{ amount: 50, categoryId: 2 },
],
});
Expense Reports
const reports = client.expenseReports;
const list = await reports.list({ status: "draft", limit: 25 });
const report = await reports.getById(reportId);
const mine = await reports.getMine({ status: "submitted" });
const summary = await reports.getSummary();
const pending = await reports.getPendingApprovals();
Lifecycle
const created = await reports.create({ name: "Q1 Travel" });
await reports.update(reportId, { name: "Q1 Travel - Updated" });
await reports.submit(reportId);
await reports.approve(reportId, { comment: "Looks good!" });
await reports.reject(reportId, { reason: "Missing receipts" });
await reports.requestChanges(reportId, { comment: "Please add hotel receipt" });
await reports.recall(reportId);
await reports.reimburse(reportId);
await reports.planReimbursement(reportId, { scheduledDate: "2025-03-15" });
Timeline & Audit
const timeline = await reports.getTimeline(reportId);
const auditLog = await reports.getAuditLog(reportId);
await reports.addComment(reportId, { content: "Updated totals" });
Accounting
await reports.assignLedgerAccount(reportId, { ledgerAccountId: 100 });
await reports.assignExpenseLedgerAccount(reportId, expenseId, {
ledgerAccountId: 100,
});
Bills
const bills = client.bills;
const list = await bills.list({ limit: 25 });
const bill = await bills.getById(billId);
const created = await bills.create({
vendorId: 10,
amount: 1500,
dueDate: "2025-04-01",
});
await bills.update(billId, { amount: 1600 });
await bills.delete(billId);
Bill Status Workflow
const statusSummary = await bills.getStatusSummary();
const allowedTransitions = await bills.getAllowedTransitions(billId);
await bills.submitForApproval(billId);
await bills.approve(billId, { comment: "Approved" });
await bills.schedulePayment(billId, { paymentDate: "2025-04-01" });
await bills.markPaid(billId);
await bills.cancel(billId, { reason: "Duplicate" });
await bills.revertToDraft(billId);
await bills.reschedulePayment(billId, { paymentDate: "2025-04-15" });
Bulk Bill Operations
await bills.bulkApprove({ billIds: [1, 2, 3] });
await bills.bulkMarkPaid({ billIds: [1, 2] });
await bills.bulkSchedulePayment({ billIds: [1, 2], paymentDate: "2025-04-01" });
await bills.bulkDelete({ billIds: [1, 2, 3] });
Vendors
const vendors = client.vendors;
const list = await vendors.list({ limit: 25 });
const vendor = await vendors.getById(vendorId);
const created = await vendors.create({
name: "Office Depot",
email: "[email protected]",
});
await vendors.update(vendorId, { name: "Office Depot Inc." });
await vendors.delete(vendorId);
Vendor Business Operations
const businessVendors = await vendors.listBusiness();
const businessVendor = await vendors.getBusinessVendor(vendorId);
await vendors.createBusiness({ name: "New Supplier" });
await vendors.updateBusiness(vendorId, { name: "Updated Supplier" });
await vendors.deleteBusiness(vendorId);
await vendors.refreshBusiness(vendorId);
Batch & Import
await vendors.batchCreate([
{ name: "Vendor A" },
{ name: "Vendor B" },
]);
await vendors.bulkDelete({ vendorIds: [1, 2, 3] });
await vendors.merge({ sourceVendorId: 2, targetVendorId: 1 });
const result = await vendors.importCsv(csvFile);
const template = await vendors.downloadImportTemplate();
Businesses
const businesses = client.businesses;
const list = await businesses.list({ limit: 10 });
const business = await businesses.getById(businessId);
const count = await businesses.count();
const created = await businesses.create({
name: "Acme Corp",
currency: "CAD",
});
await businesses.update(businessId, { name: "Acme Corporation" });
await businesses.delete(businessId);
await businesses.createBrand(businessId, { name: "Acme Brand" });
All Available Services
| Property | Service | Description |
|---|---|---|
users | UserService | User profile and account management |
businesses | BusinessService | Business CRUD and listing |
businessUsers | BusinessUserService | Business user management |
invitations | InvitationService | Team invitations |
memberships | MembershipService | Business memberships |
receipts | ReceiptService | Receipt CRUD and OCR processing |
bills | BillService | Bill management and approval workflow |
transactions | TransactionService | Transaction history |
expenses | ExpenseService | Expense management with bulk operations |
expenseReports | ExpenseReportService | Expense report lifecycle |
expenseReportExpenses | ExpenseReportExpenseService | Report-expense associations |
expenseReportPayments | ExpenseReportPaymentService | Report payment tracking |
expenseJobs | ExpenseJobService | Background processing jobs |
approbations | ApprobationService | Approval management |
vendors | VendorService | Vendor management with import/merge |
vendorConnections | VendorConnectionService | Vendor connection management |
notifications | NotificationService | In-app notifications |
paymentMethods | PaymentMethodService | Payment method management |
integrations | IntegrationService | Third-party integrations |
appInstallations | AppInstallationService | App installation management |
emailAccounts | EmailAccountService | Email account configuration |
reports | ReportingService | Reporting and analytics |
checkout | CheckoutService | Checkout flows |
connect | ConnectService | Connect account management |
locations | LocationService | Location management |
tables | TableService | Table management |
billing | BillingService | Subscription and billing |
workflows | WorkflowService | Automation workflows |
customers | CustomerService | Customer management |
departments | DepartmentService | Department management |
products | ProductService | Product catalog |
taxes | TaxService | Tax configuration |
promoCodes | PromoCodeService | Promo code management |
categories | CategoryService | Expense category management |
employees | EmployeeService | Employee management |
attachments | AttachmentService | File attachment management |
emailForwarding | EmailForwardingService | Email forwarding rules |
authorizedSenders | AuthorizedSenderService | Authorized sender management |
loyalty | LoyaltyService | Loyalty program management |
Error Handling
import {
SmartbillsError,
SmartbillsApiError,
SmartbillsValidationError,
SmartbillsAuthenticationError,
SmartbillsPermissionError,
SmartbillsNotFoundError,
SmartbillsRateLimitError,
SmartbillsNetworkError,
SmartbillsConflictError,
SmartbillsQuotaError,
isSmartbillsError,
isRetryableError,
} from "@smartbills/sdk";
try {
const expense = await client.expenses.getById(expenseId);
} catch (error) {
if (isSmartbillsError(error)) {
if (error instanceof SmartbillsAuthenticationError) {
console.error("Authentication failed:", error.message);
} else if (error instanceof SmartbillsValidationError) {
error.errors.forEach((fieldError) => {
console.error(`${fieldError.field}: ${fieldError.message}`);
});
} else if (error instanceof SmartbillsNotFoundError) {
console.error("Resource not found:", error.message);
} else if (error instanceof SmartbillsRateLimitError) {
console.error("Rate limited - retry later");
} else if (error instanceof SmartbillsPermissionError) {
console.error("Insufficient permissions:", error.message);
} else if (error instanceof SmartbillsNetworkError) {
console.error("Network error:", error.message);
}
}
}
Error Types
| Error Class | HTTP Status | Description |
|---|---|---|
SmartbillsError | — | Base error class |
SmartbillsApiError | Various | Generic API error |
SmartbillsValidationError | 400 | Field-level validation errors |
SmartbillsAuthenticationError | 401 | Invalid or expired token |
SmartbillsPermissionError | 403 | Insufficient permissions |
SmartbillsNotFoundError | 404 | Resource not found |
SmartbillsConflictError | 409 | Resource conflict |
SmartbillsRateLimitError | 429 | Rate limit exceeded |
SmartbillsQuotaError | — | Quota exceeded |
SmartbillsNetworkError | — | Network connectivity issues |
Type Guards
import {
isSmartbillsError,
isApiError,
isValidationError,
isRateLimitError,
isNetworkError,
isAuthenticationError,
isPermissionError,
isNotFoundError,
isConflictError,
isQuotaError,
isRetryableError,
} from "@smartbills/sdk";
Pagination
All list endpoints return a standard paginated response:interface SBListResponse<T> {
data: T[];
pagination: SBPagination;
}
interface SBPagination {
count: number;
limit: number;
currentPage: number;
pageCount: number;
}
const page1 = await client.expenses.listBusiness({ page: 1, limit: 25 });
console.log(page1.pagination.count);
console.log(page1.pagination.pageCount);
if (page1.pagination.currentPage < page1.pagination.pageCount) {
const page2 = await client.expenses.listBusiness({ page: 2, limit: 25 });
}
Sorting
const sorted = await client.expenses.listBusiness({
sortBy: "createdAt",
sortDirection: "desc",
limit: 25,
});
Common Types
import type {
SBEntity,
SBTimestamps,
SBListResponse,
SBPagination,
PaginationRequest,
SBAddress,
SBCoordinate,
SBMoney,
SBBillingAddress,
SBBatchResponse,
SBBulkActionResponse,
SBFileDownloadResponse,
RequestOptions,
} from "@smartbills/sdk";
File Uploads
S3 Presigned Upload (Recommended)
import { uploadFileToS3 } from "@smartbills/sdk";
const presigned = await client.expenses.presignUpload({
fileName: "receipt.jpg",
contentType: "image/jpeg",
});
await uploadFileToS3(presigned.url, presigned.fields, file);
await client.expenses.confirmUpload({ key: presigned.fields.key });
Direct FormData Upload
const formData = new FormData();
formData.append("files", fileInput.files[0]);
const results = await client.expenses.uploadBusiness(formData);
TypeScript Support
The SDK is written in TypeScript and ships with full type definitions. All entity types, request types, and response types are exported:import type {
SBTransaction,
SBExpenseReport,
SBBusiness,
SBVendor,
SBBill,
ExpenseReportStatus,
BillStatus,
PayerType,
MembershipRole,
} from "@smartbills/sdk";
Examples
Express.js Integration
import express from "express";
import { SmartbillsClient } from "@smartbills/sdk";
const app = express();
const client = new SmartbillsClient({
accessToken: process.env.SMARTBILLS_ACCESS_TOKEN,
businessId: parseInt(process.env.SMARTBILLS_BUSINESS_ID!),
});
app.get("/expenses", async (req, res) => {
const expenses = await client.expenses.listBusiness({
page: parseInt(req.query.page as string) || 1,
limit: 25,
});
res.json(expenses);
});
app.listen(3000);
Next.js Server Action
"use server";
import { SmartbillsClient } from "@smartbills/sdk";
const client = new SmartbillsClient({
accessToken: process.env.SMARTBILLS_ACCESS_TOKEN,
businessId: parseInt(process.env.SMARTBILLS_BUSINESS_ID!),
});
export async function getExpenses(page: number = 1) {
return client.expenses.listBusiness({ page, limit: 25 });
}
export async function createExpenseReport(name: string, expenseIds: number[]) {
return client.expenseReports.create({ name });
}
Use environment variables — Never hardcode access tokens in your source code.
Handle errors gracefully — Always wrap API calls in try-catch blocks and use the type guards to handle specific error types.
Use TypeScript — Take advantage of full type safety for better developer experience and catch issues at compile time.