@thegraphitelab/n8n-nodes-servicetitan 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -12,13 +12,15 @@ More resources (Job, Invoice, Estimate, Technician, Booking, etc.) get added per
|
|
|
12
12
|
|
|
13
13
|
## Credentials
|
|
14
14
|
|
|
15
|
-
Required fields:
|
|
15
|
+
Required UI fields (per-client, set when creating a credential in n8n):
|
|
16
16
|
- **Environment** — `Integration (Sandbox)` or `Production`
|
|
17
17
|
- **Tenant ID** — found in ServiceTitan → Settings → API Application Access
|
|
18
|
-
- **App Key** — `ST-App-Key`. TGL-owned application identifier. Same across all TGL clients (controlled by TGL's ServiceTitan developer portal app).
|
|
19
18
|
- **Client ID** — OAuth client ID, per-client
|
|
20
19
|
- **Client Secret** — OAuth client secret, per-client
|
|
21
20
|
|
|
21
|
+
Required env var (set once per n8n instance, on **both** primary and worker services):
|
|
22
|
+
- **`TGL_SERVICETITAN_APP_KEY`** — the `ST-App-Key`. TGL-owned application identifier, same across all TGL clients. Sourced from the TGL ServiceTitan developer portal app. Read from the env at request time, not stored per-credential.
|
|
23
|
+
|
|
22
24
|
The credential's "Test" button hits `/settings/v2/tenant/{tenantId}/user-roles?page=1&pageSize=1` to verify the full auth chain works.
|
|
23
25
|
|
|
24
26
|
## Auth flow
|
|
@@ -8,6 +8,7 @@ export declare class ServiceTitanApi implements ICredentialType {
|
|
|
8
8
|
properties: INodeProperties[];
|
|
9
9
|
preAuthentication(this: IHttpRequestHelper, credentials: ICredentialDataDecryptedObject): Promise<{
|
|
10
10
|
accessToken: string;
|
|
11
|
+
appKey: string;
|
|
11
12
|
}>;
|
|
12
13
|
authenticate: IAuthenticateGeneric;
|
|
13
14
|
test: ICredentialTestRequest;
|
|
@@ -42,15 +42,6 @@ class ServiceTitanApi {
|
|
|
42
42
|
required: true,
|
|
43
43
|
description: 'Found in ServiceTitan → Settings → API Application Access. Treated as a string in URL paths.',
|
|
44
44
|
},
|
|
45
|
-
{
|
|
46
|
-
displayName: 'App Key',
|
|
47
|
-
name: 'appKey',
|
|
48
|
-
type: 'string',
|
|
49
|
-
typeOptions: { password: true },
|
|
50
|
-
default: '',
|
|
51
|
-
required: true,
|
|
52
|
-
description: 'The ST-App-Key. TGL-owned application identifier — the same value is used across all TGL clients. Get this from the TGL ServiceTitan developer portal application.',
|
|
53
|
-
},
|
|
54
45
|
{
|
|
55
46
|
displayName: 'Client ID',
|
|
56
47
|
name: 'clientId',
|
|
@@ -68,7 +59,8 @@ class ServiceTitanApi {
|
|
|
68
59
|
required: true,
|
|
69
60
|
description: "Per-client OAuth client secret from ServiceTitan → Settings → API Application Access.",
|
|
70
61
|
},
|
|
71
|
-
//
|
|
62
|
+
// Both fields below are populated by preAuthentication, not user input.
|
|
63
|
+
// appKey is read from the TGL_SERVICETITAN_APP_KEY env var on the n8n service.
|
|
72
64
|
{
|
|
73
65
|
displayName: 'Access Token',
|
|
74
66
|
name: 'accessToken',
|
|
@@ -76,25 +68,77 @@ class ServiceTitanApi {
|
|
|
76
68
|
typeOptions: { password: true },
|
|
77
69
|
default: '',
|
|
78
70
|
},
|
|
71
|
+
{
|
|
72
|
+
displayName: 'App Key',
|
|
73
|
+
name: 'appKey',
|
|
74
|
+
type: 'hidden',
|
|
75
|
+
typeOptions: { password: true },
|
|
76
|
+
default: '',
|
|
77
|
+
},
|
|
79
78
|
];
|
|
80
79
|
async preAuthentication(credentials) {
|
|
80
|
+
// The ST-App-Key is TGL-owned and identical across all clients, so it
|
|
81
|
+
// belongs at the n8n instance level, not on every credential.
|
|
82
|
+
// eslint-disable-next-line @n8n/community-nodes/no-restricted-globals
|
|
83
|
+
const appKey = process.env.TGL_SERVICETITAN_APP_KEY;
|
|
84
|
+
if (!appKey) {
|
|
85
|
+
throw new Error('[ServiceTitan auth] TGL_SERVICETITAN_APP_KEY env var is not set on this n8n service. Set it on BOTH the n8n primary and worker services, then redeploy.');
|
|
86
|
+
}
|
|
81
87
|
const environment = credentials.environment ?? 'integration';
|
|
82
88
|
const clientId = credentials.clientId;
|
|
83
89
|
const clientSecret = credentials.clientSecret;
|
|
90
|
+
const tenantId = credentials.tenantId;
|
|
91
|
+
const missing = [];
|
|
92
|
+
if (!clientId)
|
|
93
|
+
missing.push('Client ID');
|
|
94
|
+
if (!clientSecret)
|
|
95
|
+
missing.push('Client Secret');
|
|
96
|
+
if (!tenantId)
|
|
97
|
+
missing.push('Tenant ID');
|
|
98
|
+
if (missing.length > 0) {
|
|
99
|
+
throw new Error(`[ServiceTitan auth] Missing required credential field(s): ${missing.join(', ')}. Fill them in on the n8n credential and save.`);
|
|
100
|
+
}
|
|
101
|
+
const authHost = getAuthHost(environment);
|
|
102
|
+
const tokenUrl = `https://${authHost}/connect/token`;
|
|
103
|
+
// Diagnostics — env, host, clientId prefix (NOT secret), App Key prefix
|
|
104
|
+
const clientIdPreview = `${clientId.slice(0, 6)}…${clientId.slice(-4)}`;
|
|
105
|
+
const appKeyPreview = `${appKey.slice(0, 6)}…${appKey.slice(-4)}`;
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(`[ServiceTitan auth] env=${environment} tokenUrl=${tokenUrl} tenantId=${tenantId} clientId=${clientIdPreview} appKey=${appKeyPreview}`);
|
|
84
108
|
const body = new URLSearchParams({
|
|
85
109
|
grant_type: 'client_credentials',
|
|
86
110
|
client_id: clientId,
|
|
87
111
|
client_secret: clientSecret,
|
|
88
112
|
}).toString();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
response = (await this.helpers.httpRequest({
|
|
116
|
+
method: 'POST',
|
|
117
|
+
url: tokenUrl,
|
|
118
|
+
headers: {
|
|
119
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
120
|
+
},
|
|
121
|
+
body,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// Surface ServiceTitan's actual error response so users can fix it
|
|
126
|
+
const e = err;
|
|
127
|
+
const status = e.response?.status;
|
|
128
|
+
const statusText = e.response?.statusText;
|
|
129
|
+
const data = e.response?.data;
|
|
130
|
+
const bodyStr = typeof data === 'string' ? data : JSON.stringify(data);
|
|
131
|
+
const detail = bodyStr ? ` body=${bodyStr.slice(0, 500)}` : '';
|
|
132
|
+
throw new Error(`[ServiceTitan auth] Token fetch failed: ${status ?? '?'} ${statusText ?? ''}${detail}. ` +
|
|
133
|
+
`URL=${tokenUrl} env=${environment} clientId=${clientIdPreview}. ` +
|
|
134
|
+
`Verify Client ID/Secret are correct for the ${environment} environment.`);
|
|
135
|
+
}
|
|
136
|
+
if (!response?.access_token) {
|
|
137
|
+
throw new Error(`[ServiceTitan auth] Token endpoint returned 2xx but no access_token. Response: ${JSON.stringify(response).slice(0, 500)}`);
|
|
138
|
+
}
|
|
139
|
+
// eslint-disable-next-line no-console
|
|
140
|
+
console.log(`[ServiceTitan auth] Token fetch OK. expires_in=${response.expires_in}s`);
|
|
141
|
+
return { accessToken: response.access_token, appKey };
|
|
98
142
|
}
|
|
99
143
|
authenticate = {
|
|
100
144
|
type: 'generic',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceTitanApi.credentials.js","sourceRoot":"","sources":["../../credentials/ServiceTitanApi.credentials.ts"],"names":[],"mappings":";;;AAoBA,gCAIC;AAfD;;;;GAIG;AACH,SAAS,WAAW,CAAC,WAAmB;IACvC,OAAO,WAAW,KAAK,YAAY;QAClC,CAAC,CAAC,sBAAsB;QACxB,CAAC,CAAC,kCAAkC,CAAC;AACvC,CAAC;AAED,SAAgB,UAAU,CAAC,WAAmB;IAC7C,OAAO,WAAW,KAAK,YAAY;QAClC,CAAC,CAAC,qBAAqB;QACvB,CAAC,CAAC,iCAAiC,CAAC;AACtC,CAAC;AAED,MAAa,eAAe;IAC3B,IAAI,GAAG,iBAAiB,CAAC;IAEzB,WAAW,GAAG,kBAAkB,CAAC;IAEjC,IAAI,GAAG,6CAAsD,CAAC;IAE9D,gBAAgB,GAAG,oCAAoC,CAAC;IAExD,UAAU,GAAsB;QAC/B;YACC,WAAW,EAAE,aAAa;YAC1B,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,SAAS;YACf,OAAO,EAAE;gBACR,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,aAAa,EAAE;gBACvD,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;aAC3C;YACD,OAAO,EAAE,aAAa;YACtB,WAAW,EACV,yJAAyJ;SAC1J;QACD;YACC,WAAW,EAAE,WAAW;YACxB,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,WAAW,EACV,8FAA8F;SAC/F;QACD;YACC,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"ServiceTitanApi.credentials.js","sourceRoot":"","sources":["../../credentials/ServiceTitanApi.credentials.ts"],"names":[],"mappings":";;;AAoBA,gCAIC;AAfD;;;;GAIG;AACH,SAAS,WAAW,CAAC,WAAmB;IACvC,OAAO,WAAW,KAAK,YAAY;QAClC,CAAC,CAAC,sBAAsB;QACxB,CAAC,CAAC,kCAAkC,CAAC;AACvC,CAAC;AAED,SAAgB,UAAU,CAAC,WAAmB;IAC7C,OAAO,WAAW,KAAK,YAAY;QAClC,CAAC,CAAC,qBAAqB;QACvB,CAAC,CAAC,iCAAiC,CAAC;AACtC,CAAC;AAED,MAAa,eAAe;IAC3B,IAAI,GAAG,iBAAiB,CAAC;IAEzB,WAAW,GAAG,kBAAkB,CAAC;IAEjC,IAAI,GAAG,6CAAsD,CAAC;IAE9D,gBAAgB,GAAG,oCAAoC,CAAC;IAExD,UAAU,GAAsB;QAC/B;YACC,WAAW,EAAE,aAAa;YAC1B,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,SAAS;YACf,OAAO,EAAE;gBACR,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,aAAa,EAAE;gBACvD,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;aAC3C;YACD,OAAO,EAAE,aAAa;YACtB,WAAW,EACV,yJAAyJ;SAC1J;QACD;YACC,WAAW,EAAE,WAAW;YACxB,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,WAAW,EACV,8FAA8F;SAC/F;QACD;YACC,WAAW,EAAE,WAAW;YACxB,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,mFAAmF;SAChG;QACD;YACC,WAAW,EAAE,eAAe;YAC5B,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC/B,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,uFAAuF;SACpG;QACD,wEAAwE;QACxE,+EAA+E;QAC/E;YACC,WAAW,EAAE,cAAc;YAC3B,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC/B,OAAO,EAAE,EAAE;SACX;QACD;YACC,WAAW,EAAE,SAAS;YACtB,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC/B,OAAO,EAAE,EAAE;SACX;KACD,CAAC;IAEF,KAAK,CAAC,iBAAiB,CAA2B,WAA2C;QAC5F,sEAAsE;QACtE,8DAA8D;QAC9D,sEAAsE;QACtE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;QACpD,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACd,yJAAyJ,CACzJ,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAI,WAAW,CAAC,WAAsB,IAAI,aAAa,CAAC;QACzE,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAkB,CAAC;QAChD,MAAM,YAAY,GAAG,WAAW,CAAC,YAAsB,CAAC;QACxD,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAkB,CAAC;QAEhD,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,CAAC,YAAY;YAAE,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACjD,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACd,6DAA6D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,gDAAgD,CAC/H,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,WAAW,QAAQ,gBAAgB,CAAC;QAErD,wEAAwE;QACxE,MAAM,eAAe,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,aAAa,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,sCAAsC;QACtC,OAAO,CAAC,GAAG,CACV,2BAA2B,WAAW,aAAa,QAAQ,aAAa,QAAQ,aAAa,eAAe,WAAW,aAAa,EAAE,CACtI,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;YAChC,UAAU,EAAE,oBAAoB;YAChC,SAAS,EAAE,QAAQ;YACnB,aAAa,EAAE,YAAY;SAC3B,CAAC,CAAC,QAAQ,EAAE,CAAC;QAEd,IAAI,QAAsD,CAAC;QAC3D,IAAI,CAAC;YACJ,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;gBAC1C,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,QAAQ;gBACb,OAAO,EAAE;oBACR,cAAc,EAAE,mCAAmC;iBACnD;gBACD,IAAI;aACJ,CAAC,CAAiD,CAAC;QACrD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,mEAAmE;YACnE,MAAM,CAAC,GAAG,GAIT,CAAC;YACF,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC;YAClC,MAAM,UAAU,GAAG,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC;YAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC;YAC9B,MAAM,OAAO,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACvE,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CACd,2CAA2C,MAAM,IAAI,GAAG,IAAI,UAAU,IAAI,EAAE,GAAG,MAAM,IAAI;gBACxF,OAAO,QAAQ,QAAQ,WAAW,aAAa,eAAe,IAAI;gBAClE,+CAA+C,WAAW,eAAe,CAC1E,CAAC;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACd,kFAAkF,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAC1H,CAAC;QACH,CAAC;QAED,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,kDAAkD,QAAQ,CAAC,UAAU,GAAG,CAAC,CAAC;QAEtF,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvD,CAAC;IAED,YAAY,GAAyB;QACpC,IAAI,EAAE,SAAS;QACf,UAAU,EAAE;YACX,OAAO,EAAE;gBACR,aAAa,EAAE,sCAAsC;gBACrD,YAAY,EAAE,0BAA0B;aACxC;SACD;KACD,CAAC;IAEF,IAAI,GAA2B;QAC9B,OAAO,EAAE;YACR,OAAO,EACN,oHAAoH;YACrH,GAAG,EAAE,2DAA2D;YAChE,MAAM,EAAE,KAAK;YACb,EAAE,EAAE;gBACH,IAAI,EAAE,CAAC;gBACP,QAAQ,EAAE,CAAC;aACX;SACD;KACD,CAAC;CACF;AA5KD,0CA4KC"}
|
package/dist/package.json
CHANGED