@masonlandcattle/servicetitan-sdk
v0.9.6
Published
ServiceTitan API SDK for Node.js and TypeScript with retries, rate limiting, pagination, and broad tenant API coverage.
Maintainers
Readme
ServiceTitan SDK (Node.js / TypeScript)
A pragmatic ServiceTitan API client with:
- Robust auth (Client Credentials)
- Retries with exponential backoff + jitter
- Honors Retry-After, handles 429/5xx
- Built-in rate limiting with queued waiting in the client transport
- URL builder and pagination helpers (
getAll) - Broad tenant API namespace coverage across the current ServiceTitan developer portal
- Exported TypeScript helper types for better IntelliSense when using the SDK
The package supports both:
- a namespaced client via
createClient(...) - direct functional imports per namespace when preferred
Release status
Current release focus:
- Synced the SDK to the current ServiceTitan tenant API namespace set
- Corrected stale routes, verbs, and path segments across multiple resources
- Added missing documented resource modules and helpers
- Expanded exported TypeScript helper types to improve IntelliSense
Recommended release posture for the current line:
0.9.xis the stabilization phase after the broad OpenAPI alignment pass- the current public surface is intended to be much closer to the local OpenAPI specs than earlier releases
- future breaking changes should mostly be reserved for upstream ServiceTitan API changes or clear correctness fixes
Repo notes
If you are editing this SDK with Codex or another coding agent, use AGENTS.md as the repo-specific guide.
The short version:
- local OpenAPI JSON files are the source of truth
- breaking changes are acceptable when the schema and SDK disagree
- prefer namespace-specific types over generic placeholders
- keep
src/types/resources.tsfor shared base helpers only - run
npm run buildafter changes
Install
npm install @masonlandcattle/servicetitan-sdkConfigure
Set env vars or pass options:
TENANT_IDAPP_KEYCLIENT_IDSECRET_KEY- Optional:
ENVIRONMENT=production|development
Usage (namespaced client)
import { createClient } from "@masonlandcattle/servicetitan-sdk";
const st = createClient({
tenantId: process.env.TENANT_ID!,
appKey: process.env.APP_KEY!,
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.SECRET_KEY!,
environment: (process.env.ENVIRONMENT as any) || "production",
retries: 3,
apiRateLimitPerSecond: 45,
maxConcurrent: 20,
});
// List jobs (single page)
const page = await st.jpm.listJobs({ page: 1, pageSize: 100 });
// List all jobs server-side (aggregates pages)
const allJobs = await st.jpm.listJobs({ jobStatus: "Scheduled" }, { all: true, pageSize: 500 });
// Create a job note
await st.jpm.createJobNote(123456, { text: "Hello from SDK", pinToTop: true });
// Materials (with get-all)
const materials = await st.pricebook.listMaterials({}, { all: true, pageSize: 500 });Important behaviors
- Local OpenAPI JSON files in
ServiceTitanOpenAPIJsonare the source of truth for this SDK. - Breaking changes are allowed when the documented ServiceTitan contract and the previous SDK surface disagree.
- Requests are throttled inside
src/client.ts, so callers do not need to add their own queueing to use helpers likeclient.request(...)orclient.getAll(...). - Normal tenant API traffic is queued at
45requests per second per tenant by default. You can lower it or raise it during client creation, but the SDK clampsapiRateLimitPerSecondto55max. - Reporting runs made through
st.reporting.getReportData(...)orst.reporting.startReportDataQuery(...)are additionally queued at5runs per minute for the sametenant + report category + report id. - When a limit is exceeded, the SDK waits in an in-memory queue instead of failing immediately. This means large
getAll()calls or bursts of individual requests may take longer to complete under load. - If ServiceTitan still responds with
429, the client honorsRetry-Afterwhen present and retries with exponential backoff as a fallback. - Export feeds generally use
{ from, includeRecentChanges }continuation params and return{ data, hasMore, continueFrom }. - Binary telecom media endpoints return
ArrayBuffer. options.allis only used on endpoints with safe paginateddataplushasMorebehavior.
Known limits
- Webhook verification helpers are not included yet. The public docs available in this repo workflow are not specific enough to safely implement the exact signature contract.
- Some nested model fields are still intentionally broad where the upstream schema is weak or highly open-ended.
- This package is an API SDK, not an application framework. Queueing, persistence, workflow orchestration, and webhook hosting should stay in the consuming app.
- ServiceTitan may change upstream schemas over time. When that happens, the local OpenAPI JSON files should be updated first, then the SDK should be realigned to match.
Docs map
README.md: install, usage, examples, and package behaviorAGENTS.md: repo-specific engineering rules for Codex and contributorsdocs/architecture.md: structure, boundaries, and extension patterns
Resources and quick examples
Below is a brief tour of the main resource namespaces. Each list function supports { all?: boolean; pageSize?: number } to fetch all pages server‑side.
Initialize once and reuse:
import { createClient } from "@masonlandcattle/servicetitan-sdk";
const st = createClient({
tenantId: process.env.TENANT_ID!,
appKey: process.env.APP_KEY!,
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.SECRET_KEY!,
});Accounting (st.accounting)
- Invoices, Payments, GL Accounts, Inventory Bills, AP Credits/Payments, Tax Zones, etc.
const invoices = await st.accounting.listInvoices({ customerId: 1234 }, { all: true });
const invoicesById = await st.accounting.listInvoicesByIds([111, 222, 333]);
await st.accounting.markInvoicesAsExported([111, 222]);
const gl = await st.accounting.listGlAccounts({ type: "Asset" }, { all: true });CRM (st.crm)
- Customers, Contacts, Locations, Leads, Bookings.
const customers = await st.crm.listCustomers({ search: "Acme" }, { all: true });
const contacts = await st.crm.listContacts({ updatedAfter: "2024-01-01" }, { all: true });
const locations = await st.crm.listLocations({ customerId: 1234 }, { all: true });Dispatch (st.dispatch)
- Teams, Zones, Arrival Windows, Technician Shifts, Appointment Assignments.
const teams = await st.dispatch.listTeams({}, { all: true });
const windows = await st.dispatch.listArrivalWindows({ date: "2025-01-01" }, { all: true });Equipment Systems (st.equipmentSystems)
- Installed Equipment, installed equipment systems, and metadata.
const eq = await st.equipmentSystems.listInstalledEquipment({ search: "Carrier" }, { all: true });
const systems = await st.equipmentSystems.listEquipmentSystems({}, { all: true });
await st.equipmentSystems.addEquipmentToSystem(123, { equipmentIds: [98765] });Findings (st.findings)
- Findings, finding assets, and finding attachments.
import type {
FindingCreatePayload,
FindingListResponse,
} from "@masonlandcattle/servicetitan-sdk";
const findings = await st.findings.listFindings({ page: 1, pageSize: 50 });
const assets = await st.findings.listFindingAssets({ page: 1, pageSize: 50 });
const findingPayload: FindingCreatePayload = {
name: "Loose wire",
summary: "Panel inspection issue",
locationId: 1234,
};
const created = await st.findings.createFinding(findingPayload);
const attachment = await st.findings.createFindingAttachment(created.id!, {
fileName: "inspection-photo.jpg",
contentType: "image/jpeg",
url: "https://example.com/inspection-photo.jpg",
});Useful Findings exports for IntelliSense:
FindingCreatePayloadFindingUpdatePayloadFindingListParamsFindingListResponseFindingAssetListParamsFindingAssetListResponseFindingSummaryFindingAssetFindingAttachment
Forms (st.forms)
- Forms, Jobs + Forms, Submissions.
const forms = await st.forms.listForms({}, { all: true });Inventory (st.inventory)
- Purchase Orders (+ types/markups/requests), Receipts, Transfers, Returns, Trucks, Vendors, Warehouses.
const pos = await st.inventory.listPurchaseOrders({ status: "Open" }, { all: true });
const receipts = await st.inventory.listReceipts({ dateFrom: "2025-01-01" }, { all: true });JPM (Jobs/Projects) (st.jpm)
- Jobs, Appointments, appointment summaries, job equipment attachments, Projects, WBS, types/statuses.
const jobs = await st.jpm.listJobs({ jobStatus: "Scheduled" }, { all: true });
const appts = await st.jpm.listAppointments({ startsOnOrAfter: "2025-01-01" }, { all: true });
const jobEquipment = await st.jpm.attachJobEquipment(123456, { equipmentIds: [98765] });Marketing (st.marketing) and Marketing Ads (st.marketingAds)
- Campaigns/Categories/Costs; Ads Attributions, Performance, and Capacity Warnings.
const campaigns = await st.marketing.listCampaigns({}, { all: true });
await st.marketingAds.createWebLeadFormAttribution({
leadId: 42,
webSessionData: {
landingPageUrl: "https://example.com/landing",
referrerUrl: "https://google.com",
utmSource: "google",
utmMedium: "cpc",
utmCampaign: "spring-promo",
},
});Marketing Reputation (st.marketingReputation)
- Reviews.
const reviews = await st.marketingReputation.listReviews({ createdOnOrAfter: "2025-01-01" }, { all: true });Memberships (st.memberships)
- Memberships, Types, Recurring Service Types/Events/Services, Invoice Templates.
const memberships = await st.memberships.listMemberships({ customerId: 1234 }, { all: true });Payroll (st.payroll)
- Timesheets (+ non-job, per-job), Timesheet Codes, Payrolls, Adjustments, Job Splits, Location Labor Rates.
const codes = await st.payroll.listTimesheetCodes({}, { all: true });
const payrolls = await st.payroll.listPayrolls({ createdOnOrAfter: "2025-01-01" }, { all: true });Pricebook (st.pricebook)
- Materials, Services, Equipment, Images, Discounts & Fees, Categories, Client-Specific Pricing.
const materials = await st.pricebook.listMaterials({ updatedAfter: "2025-01-01" }, { all: true });Reporting (st.reporting)
- Report Categories and mappings, Dynamic Value Sets, and async report data queries.
const cats = await st.reporting.listReportCategories({}, { all: true });
const result = await st.reporting.startReportDataQuery("operations", 123, {
parameters: [{ name: "From", value: "2026-01-01" }],
});
if ("token" in result) {
const polled = await st.reporting.getReportDataQuery(result.token);
}Sales Estimates (st.salesEstimates)
- Estimates, Estimate Items, Estimate Templates, Proposal Templates, and Proposal Types.
const ests = await st.salesEstimates.listEstimates({ jobNumber: "131179" }, { all: true });
const templates = await st.salesEstimates.listEstimateTemplates({ active: "True" }, { all: true });
await st.salesEstimates.updateEstimateItems(123, {
skuId: 456,
membershipDurationBillingId: 789,
});Scheduling Pro (st.schedulingPro)
- Routers, Schedulers.
const schedulers = await st.schedulingPro.listSchedulers({}, { all: true });Service Agreements (st.serviceAgreements)
- Agreements and export endpoints, including agreement custom field values.
const agreements = await st.serviceAgreements.listServiceAgreements({}, { all: true });Settings (st.settings)
- Employees, Technicians, Business Units, Tag Types, User Roles.
const techs = await st.settings.listTechnicians({}, { all: true });Task Management (st.taskManagement)
- Tasks and Client-Side Data.
const tasks = await st.taskManagement.listTasks({ status: "Open" }, { all: true });Telecom (st.telecom)
- Calls + media.
const calls = await st.telecom.listCalls({ createdOnOrAfter: "2025-01-01" }, { all: true });Timesheets V2 (st.timesheetsV2)
- Activities, Types, Categories.
const activities = await st.timesheetsV2.listActivities({}, { all: true });Job Bookings (st.jbce)
- Call Reasons.
const reasons = await st.jbce.listCallReasons({}, { all: true });Customer Interactions (st.customerInteractions)
- Technician Ratings.
const ratings = await st.customerInteractions.listTechnicianRatings({ createdOnOrAfter: "2025-01-01" }, { all: true });Type helpers
The package exports helper types from the root for stronger autocomplete and payload guidance. Examples:
import type {
CrmExportCustomer,
CreateEstimateRequest,
EquipmentSystem,
CreateWebLeadFormAttributionRequest,
Contact,
ContactMethodCreatePayload,
CustomerInteractionsExportResponse,
FindingCreatePayload,
Invoice,
JpmExportJob,
MarketingAdsPerformanceRecord,
ReportDataPendingResponse,
SalesEstimatesExportResponse,
TelecomExportCall,
} from "@masonlandcattle/servicetitan-sdk";Many resource methods now use typed params and payload helpers directly, so editor IntelliSense is much better than a generic Record<string, unknown> workflow.
Alternate import style (functional)
import { ServiceTitanClient, CRM } from "@masonlandcattle/servicetitan-sdk";
const st = new ServiceTitanClient({ /* creds */ });
const customers = await CRM.listCustomers(st, { createdOnOrAfter: "2024-01-01" }, { all: true });Local development / testing without publishing
Run local example scripts directly against the source using tsx.
Set environment variables:
TENANT_IDAPP_KEYCLIENT_IDSECRET_KEY
Execute examples:
npm run ex:crm
npm run ex:dispatch
npm run ex:equip
npm run ex:namespaced
npm run ex:crm-export
npm run ex:telecom-mediaThese scripts import from ../src, so there's no need to publish/install.
API Shape
/{category}/v2/tenant/{TENANT_ID}/{subject}
Use client.buildPath({ category, subject, idOrSubpath }) and client.request(method, path, { params, data }). To fetch every page server-side, pass { all: true, pageSize?: number } to supported list functions.
Contributing / Codex
If you are updating the SDK with Codex or another coding agent, read AGENTS.md first. It captures the repo conventions used for the OpenAPI alignment work.
Publish
npm run build
npm publish --access publicNot affiliated with ServiceTitan. Respect rate limits and terms.
