Skip to content

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.

  • 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 in tblTenant. Every API call is scoped …/v2/tenant/{tenantId}/….
  • App key: all requests carry an ST-App-key header — the JB Warranties ST developer app key. ⚠ Its value is hardcoded in jbw-sam/template.yaml (env blocks of JBWClientServiceTitanVerification, jbwagreementsdatamatch, jbwgetstequipmentsfortenant) and supplied to extraction via the ST_APP_KEY env var.
  • Token flow: POST {auth}/connect/token with grant_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.com for portal deep-links) and the ST integration sandbox (integration.servicetitan.com, used by stage — see jbwui stage.env).

The registered ServiceTitan application is “JB-360”, Version 5, with these OAuth scopes:

Scopes
ReadInvoice 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
WriteSame 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.

ST APIEndpointDirectionUsed byPurpose
accounting/v2GET /invoices (paged 1000, status + invoicedOnOrAfter/modifiedOnOrAfter filters)pullextraction (InvoiceService)Invoice + line-item extract → tblInvoice/tblInvoiceItem
accounting/v2GET /invoices?number=pulljbw-sam jbwinvoicejobsearch, jbwinvoicelookupInvoice lookup for admin tools / new-agreement-from-invoice
crm/v2GET /locations (pageSize 5000, resumable)pulljbw-sam jbwagreementsdatamatchLocation extract → tblSTLocationData for matching
crm/v2GET /locations/contacts?locationIds=… (250/chunk)pullextractionPhone/email enrichment of invoices
crm/v2GET/PATCH /locations/{id}pull+pushextraction, jbw-sam write-back LambdasRead existing tags/custom fields, merge, write JB tags + fields
crm/v2GET/POST/DELETE /locations/{id}/notespushextraction, jbwagreementslocationsstupload, jbwremoveduplicatelocationnotesfromst, jbwagreementreferenceremovalfromstAgreement-reference notes on locations (create, dedupe, remove)
crm/v2POST /locations (search)pulljbwupdateagreementsynclocation, jbwagreementslocationsfetch, jbwstlocationdatawithagreementLocation search for manual mapping
equipmentsystems/v2GET /installed-equipment?locationIds=…pullextraction, jbwgetstequipmentsfortenant, jbwgetstinstalledequipmentsEquipment (manufacturer/model/serial) for agreements
equipmentsystems/v2GET/PATCH /installed-equipment/{id}pushextraction, removal LambdasWarranty start/end dates + JB tags on equipment
jpm/v2GET /jobs?number=pullJBWClientServiceTitanVerificationOnboarding: verify the test job + custom fields
jpm/v2GET/PATCH /jobs/{jobId}pushextractionClaim status write-back onto ST jobs
settings/v2GET /tag-types (paged)pullJBWClientServiceTitanVerificationOnboarding: verify required tag types exist

The write-back is how technicians see warranty coverage inside ST:

ST objectWhat gets writtenWhen
Location tags”JB Ext Warranty” tagAgreement is PAID and pushed (isSentToST=0 → 1)
Location custom fields”JB Agreement Number”Same
Location notesProduct description + comments (agreement reference)Same; deduped by jbwremoveduplicatelocationnotesfromst
Installed equipmentJB tag + custom field + warranty start/end datesSame
Jobs (claims)Claim status (“Filed”/“Paid”/“Declined”) tags + custom fields: claim status/amount/date/number/paidClaim flagged isReadyForST=1 after the JBW claim-status pull
Tag/field/note removalStrips JB references when agreements expire or are cancelledDaily 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).

Verified by POST /jbwservicetitan (JBWClientServiceTitanVerification) during the jbwui wizard:

  1. Dealer creates an API application in ST (clientId/clientSecret) and supplies their tenant ID.
  2. 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).
  3. On success, tenant config lands in tblTenant + tblTenantCustomTags/tblTenantCustomFields, and the initial location/agreement pull + match runs.
FlowTriggerFrequency
Invoice/equipment pull + agreement/claim push (extraction)prod_getTenantInfo → SQS prodtenantList (one message per active tenant with its ST creds)daily
Location pull + matching (jbwagreementsdatamatchjbw_st_dataSync)UI “Sync Data” button, onboarding, admin toolson demand
Agreement-reference write-back (jbwagreementslocationsstupload)EventBridge prod-agreement-sync-with-sthourly, 5–10 AM
Tag/note removal for expired agreementsEventBridge prod-remove-agreement-tagsdaily

Full schedule detail: aws/schedules-and-queues.md.

  • 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.
  • 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 of jbw_st_dataSync.
  • The JB Warranties public API (public-api.jbwarranties.com) is the other side of every flow: agreements CRUD + JB360ClaimStatus view, api_token auth (token committed in repo config — see 03-jbw-sam.md).