ServiceTitan Integration
Everything JB360 does against the ServiceTitan API, in one place. Component internals live in 02-extraction.md and 03-jbw-sam.md; this page is the integration contract.
Connection Model
Section titled “Connection Model”- API base:
api.servicetitan.io· Auth:auth.servicetitan.io(OAuth2 client-credentials) - Per-dealer credentials: each ServiceTitan tenant (= one dealer install) has its own
clientId/clientSecret/tenantId, captured during onboarding and stored intblTenant. Every API call is scoped…/v2/tenant/{tenantId}/…. - App key: all requests carry an
ST-App-keyheader — the JB Warranties ST developer app key. ⚠ Its value is hardcoded injbw-sam/template.yaml(env blocks ofJBWClientServiceTitanVerification,jbwagreementsdatamatch,jbwgetstequipmentsfortenant) and supplied toextractionvia theST_APP_KEYenv var. - Token flow:
POST {auth}/connect/tokenwithgrant_type=client_credentials, per tenant, before each sync run (extraction:service/TokenService.java;jbw-sam: inline in each ST-calling handler). - Environments: production tenants (
go.servicetitan.comfor portal deep-links) and the ST integration sandbox (integration.servicetitan.com, used by stage — seejbwuistage.env).
App Registration (ST developer portal)
Section titled “App Registration (ST developer portal)”The registered ServiceTitan application is “JB-360”, Version 5, with these OAuth scopes:
| Scopes | |
|---|---|
| Read | Invoice Items, Invoices, Customers, Locations, Tags, Installed Equipment, Jobs, Customer Memberships, Recurring Service Events, Recurring Services, Categories, Equipment, Pricebook Images, Materials, Pricebook Bulk Operations, Services, Service Agreements, Estimates, Business Units, Tag Types |
| Write | Same full list (Invoice Items, Invoices, Customers, Locations, Tags, Installed Equipment, Jobs, Customer Memberships, Recurring Service Events, Recurring Services, Categories, Equipment, Pricebook Images, Materials, Pricebook Bulk Operations, Services, Service Agreements, Estimates, Business Units, Tag Types) |
Note for the new team: the granted scopes are far broader than what the integration actually uses (see the endpoint inventory below — reads on invoices/locations/contacts/equipment/jobs/tag-types, writes on locations/notes/equipment/jobs/tags). If ST permits, trimming write scopes to Locations, Tags, Installed Equipment, and Jobs would reduce blast radius.
Endpoint Inventory
Section titled “Endpoint Inventory”| ST API | Endpoint | Direction | Used by | Purpose |
|---|---|---|---|---|
| accounting/v2 | GET /invoices (paged 1000, status + invoicedOnOrAfter/modifiedOnOrAfter filters) | pull | extraction (InvoiceService) | Invoice + line-item extract → tblInvoice/tblInvoiceItem |
| accounting/v2 | GET /invoices?number= | pull | jbw-sam jbwinvoicejobsearch, jbwinvoicelookup | Invoice lookup for admin tools / new-agreement-from-invoice |
| crm/v2 | GET /locations (pageSize 5000, resumable) | pull | jbw-sam jbwagreementsdatamatch | Location extract → tblSTLocationData for matching |
| crm/v2 | GET /locations/contacts?locationIds=… (250/chunk) | pull | extraction | Phone/email enrichment of invoices |
| crm/v2 | GET/PATCH /locations/{id} | pull+push | extraction, jbw-sam write-back Lambdas | Read existing tags/custom fields, merge, write JB tags + fields |
| crm/v2 | GET/POST/DELETE /locations/{id}/notes | push | extraction, jbwagreementslocationsstupload, jbwremoveduplicatelocationnotesfromst, jbwagreementreferenceremovalfromst | Agreement-reference notes on locations (create, dedupe, remove) |
| crm/v2 | POST /locations (search) | pull | jbwupdateagreementsynclocation, jbwagreementslocationsfetch, jbwstlocationdatawithagreement | Location search for manual mapping |
| equipmentsystems/v2 | GET /installed-equipment?locationIds=… | pull | extraction, jbwgetstequipmentsfortenant, jbwgetstinstalledequipments | Equipment (manufacturer/model/serial) for agreements |
| equipmentsystems/v2 | GET/PATCH /installed-equipment/{id} | push | extraction, removal Lambdas | Warranty start/end dates + JB tags on equipment |
| jpm/v2 | GET /jobs?number= | pull | JBWClientServiceTitanVerification | Onboarding: verify the test job + custom fields |
| jpm/v2 | GET/PATCH /jobs/{jobId} | push | extraction | Claim status write-back onto ST jobs |
| settings/v2 | GET /tag-types (paged) | pull | JBWClientServiceTitanVerification | Onboarding: verify required tag types exist |
What JB360 Writes INTO ServiceTitan
Section titled “What JB360 Writes INTO ServiceTitan”The write-back is how technicians see warranty coverage inside ST:
| ST object | What gets written | When |
|---|---|---|
| Location tags | ”JB Ext Warranty” tag | Agreement is PAID and pushed (isSentToST=0 → 1) |
| Location custom fields | ”JB Agreement Number” | Same |
| Location notes | Product description + comments (agreement reference) | Same; deduped by jbwremoveduplicatelocationnotesfromst |
| Installed equipment | JB tag + custom field + warranty start/end dates | Same |
| Jobs (claims) | Claim status (“Filed”/“Paid”/“Declined”) tags + custom fields: claim status/amount/date/number/paid | Claim flagged isReadyForST=1 after the JBW claim-status pull |
| Tag/field/note removal | Strips JB references when agreements expire or are cancelled | Daily via prod-remove-agreement-tags → queue table tblSTRemoveAgreementTag |
Per-tenant ST tag/field IDs are stored in tblTenantCustomTags / tblTenantCustomFields (master list of required ones in tblCustomTags / tblCustomFields — currently 4 tags, 8 fields).
Onboarding Requirements (per new dealer)
Section titled “Onboarding Requirements (per new dealer)”Verified by POST /jbwservicetitan (JBWClientServiceTitanVerification) during the jbwui wizard:
- Dealer creates an API application in ST (clientId/clientSecret) and supplies their tenant ID.
- The required custom tag types and custom fields must exist on the tenant (checked against
settings/v2/tag-types, a sample job’s fields, and location custom fields). - On success, tenant config lands in
tblTenant+tblTenantCustomTags/tblTenantCustomFields, and the initial location/agreement pull + match runs.
Sync Cadence (prod)
Section titled “Sync Cadence (prod)”| Flow | Trigger | Frequency |
|---|---|---|
Invoice/equipment pull + agreement/claim push (extraction) | prod_getTenantInfo → SQS prodtenantList (one message per active tenant with its ST creds) | daily |
Location pull + matching (jbwagreementsdatamatch → jbw_st_dataSync) | UI “Sync Data” button, onboarding, admin tools | on demand |
Agreement-reference write-back (jbwagreementslocationsstupload) | EventBridge prod-agreement-sync-with-st | hourly, 5–10 AM |
| Tag/note removal for expired agreements | EventBridge prod-remove-agreement-tags | daily |
Full schedule detail: aws/schedules-and-queues.md.
Behavioral Notes & Pitfalls
Section titled “Behavioral Notes & Pitfalls”- Paging/batching: invoices 1000/page; locations 5000/page (with page-number resume in
tblSTLocationData); contact enrichment batches 250 location ids per request. Large tenants make the location pull the slow step. - Tag/field merge semantics: the write-back GETs the location first and merges JB tags/fields with existing ones — a plain PATCH would wipe dealer-managed tags.
- Rate limits: no explicit rate-limit/backoff handling exists in either module; failures surface as per-record exceptions (caught and logged per agreement/claim, so one failure doesn’t abort a batch).
- Skip-equipment tenants: tenants with
skipEquipment=1(e.g. “Mitsubishi/Multiple Models” configurations) bypass equipment validation when auto-processing agreements. - Claims matching fallback: claims are matched to ST jobs by
jbwClaimId, else by job number + dealer + agreement (FIND_IN_SET). - Secrets: the ST app key is committed in the repo (see Connection Model); per-tenant client secrets travel in SQS message bodies and are stored in
tblTenant— both worth revisiting.
Related
Section titled “Related”- Workiz is the secondary CRM: same pattern (pull job-locations, match, no write-back of tags), token-in-URL auth, handled by the
jbwworkiz*Lambdas and the_platform='Workiz'branch ofjbw_st_dataSync. - The JB Warranties public API (
public-api.jbwarranties.com) is the other side of every flow: agreements CRUD +JB360ClaimStatusview,api_tokenauth (token committed in repo config — see 03-jbw-sam.md).