servicetitan

n8n custom node for ServiceTitan — TGL internal use.

Package Information

Released: this month
Downloads: 98 weekly / 3,652 monthly
Latest Version: 0.16.1
Author: The Graphite Lab

Documentation

@thegraphitelab/n8n-nodes-servicetitan

n8n custom node package for ServiceTitan. Built by The Graphite Lab, published publicly on npmjs.org under the @thegraphitelab scope. Primary consumer is TGL's Railway-hosted n8n fleet; the package is openly browsable.

Current version: 0.6.0

Installation

In each n8n instance, go to Settings → Community Nodes → Install and enter:

@thegraphitelab/n8n-nodes-servicetitan

Accept the disclaimer and install.

Required env var

Set on the n8n service (both primary and worker if running queue mode):

TGL_SERVICETITAN_APP_KEY=<TGL ServiceTitan ST-App-Key>

This is the TGL-owned ST-App-Key from our ServiceTitan developer portal app — same across all TGL clients, sourced from env so the value never sits in client-visible credential fields. See the repo root README for the broader rationale.

What ships today

Credential: ServiceTitan API

  • OAuth 2.0 client_credentials grant
  • ST-App-Key header sourced from TGL_SERVICETITAN_APP_KEY
  • Environment switch: Integration (Sandbox) vs Production
  • preAuthentication caches an expirable accessToken in the credential data (15min TTL)
  • Test endpoint: /settings/v2/tenant/{tenantId}/user-roles?page=1&pageSize=1

UI fields (per-client, set when creating the credential in n8n):

  • Environment — Integration (Sandbox) or Production
  • Tenant ID — from ServiceTitan → Settings → API Application Access
  • Client ID — OAuth client ID, per-client
  • Client Secret — OAuth client secret, per-client

Action node: ServiceTitan

Picker label: ServiceTitan. Internal n8n name: tglServiceTitan.

Resource + Operation pattern.

Resource Operations
Customer Get by ID

The Customer.Get response includes both raw customFields[] and a flattened customFieldsByName sibling object (see Custom fields below).

Trigger node: ServiceTitan Trigger

Picker label: ServiceTitan Trigger. Sits next to the action node in n8n's node picker when the user searches "ServiceTitan".

Two-level Resource + Event picker inside the node, matching the action node's pattern. Resource determines which Event options show.

Resource Events
Job Created or Modified

Polling-based with modifiedOnOrAfter cursor and a boundary-id dedup set to avoid duplicate emissions on overlap windows.

Architecture

Manifest-driven. The shared engine lives in @thegraphitelab/n8n-nodes-core (a runtime dependency, pulled in automatically when n8n installs this package); this package supplies only ServiceTitan's specifics:

  • nodes/ServiceTitan/v1/generated/manifests.ts — the generated manifest: every resource/operation and trigger, each described declaratively by a shape. Build output from n8n-codegen — do not hand-edit.
  • nodes/ServiceTitan/v1/transport/config.ts — the TransportConfig the engine reads: credential key, base URL, pagination/cursor rules, and pathContext (maps the credential's tenantId into the {tenant} path placeholder).
  • nodes/ServiceTitan/v1/*.node.ts + descriptions/ — the action and trigger node wrappers, which call the engine's router / pollRouter and getActionProperties / getTriggerProperties.
  • credentials/ — the ServiceTitan API credential.

The engine compiles the manifest into n8n operations, poll triggers, property descriptions, and the authenticated HTTP transport. Adding or refreshing coverage is a codegen regenerate + rebuild (see the repo root README), not a hand-edit.

Custom fields behavior

ServiceTitan returns custom fields as an array of {typeId, name, value} objects. That shape is awkward to consume in downstream n8n nodes — every workflow has to write a JS-code step to find a field by name.

This package returns three shapes on every entity that has custom fields, so consumers pick what fits:

  • customFields — raw array of {typeId, name, value}, untouched. Use this if your workflow already depends on the ST shape, or you need both name + typeId together.
  • custom_fields_by_name — flat map: keys are field names, values are the raw values. Use {{ $json.custom_fields_by_name["Lockbox Code"] }} to read directly.
  • custom_fields_by_type_id — flat map: keys are stringified typeIds, values are the raw values. Use this when you want to address a field by ID (more stable than name across tenant renames).

All three coexist on every entity. None is destructive of the others.

Roadmap

Phase 1 (current): refactored framework that scales, shipping the existing Customer.Get + Jobs trigger through it.

Phase 3: port the rest of TGL's ServiceTitan Zapier operations resource-by-resource. Reference source: /Users/jacobnolley/projects/advanced_servicetitan_zapier_app (the live Zapier custom app).

Resources to port (non-exhaustive):

  • Jobs (more operations beyond the trigger)
  • Customers (full CRUD)
  • Estimates
  • Appointments
  • Invoices
  • Projects
  • Payments
  • Calls
  • Leads
  • Locations
  • Technicians
  • Vendors
  • Employees
  • Bookings

Each resource lands as a minor bump; each new operation on an existing resource is a patch.

Local dev

# From this directory
pnpm dev

Boots embedded n8n at http://localhost:5678 with this package pre-loaded and hot reload. ServiceTitan sandbox credentials go in the local n8n credential store (~/.n8n).

Reference

  • Repo root: ../../README.md
  • Recipes for adding resources, trigger events, new packages: ../../CONTRIBUTING.md
  • Source-of-truth Zapier app being ported from: /Users/jacobnolley/projects/advanced_servicetitan_zapier_app

Discussion