@commissionsight/sdk 2.0.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/dist/index.js ADDED
@@ -0,0 +1,390 @@
1
+ /** Lightweight typed client for the CommissionSight API. */
2
+ export class ApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(status, message, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = 'ApiError';
10
+ }
11
+ }
12
+ export class CommissionSightClient {
13
+ baseUrl;
14
+ token;
15
+ fetchFn;
16
+ constructor(opts) {
17
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
18
+ this.token = opts.token;
19
+ this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
20
+ }
21
+ setToken(token) {
22
+ this.token = token;
23
+ }
24
+ async request(path, init = {}) {
25
+ const headers = new Headers(init.headers);
26
+ if (this.token)
27
+ headers.set('authorization', `Bearer ${this.token}`);
28
+ if (init.body && !(init.body instanceof FormData)) {
29
+ headers.set('content-type', 'application/json');
30
+ }
31
+ const res = await this.fetchFn(`${this.baseUrl}${path}`, { ...init, headers });
32
+ const text = await res.text();
33
+ const body = text ? JSON.parse(text) : null;
34
+ if (!res.ok) {
35
+ const message = body && typeof body === 'object' && 'title' in body ? String(body.title) : res.statusText;
36
+ throw new ApiError(res.status, message, body);
37
+ }
38
+ return body;
39
+ }
40
+ /** Like `request` but returns the raw response body as text (e.g. a CSV
41
+ * download). Errors still surface as `ApiError` from the problem+json body. */
42
+ async requestText(path, init = {}) {
43
+ const headers = new Headers(init.headers);
44
+ if (this.token)
45
+ headers.set('authorization', `Bearer ${this.token}`);
46
+ const res = await this.fetchFn(`${this.baseUrl}${path}`, { ...init, headers });
47
+ const text = await res.text();
48
+ if (!res.ok) {
49
+ let body = null;
50
+ try {
51
+ body = text ? JSON.parse(text) : null;
52
+ }
53
+ catch {
54
+ // non-JSON error body — fall back to the status text
55
+ }
56
+ const message = body && typeof body === 'object' && 'title' in body
57
+ ? String(body.title)
58
+ : res.statusText;
59
+ throw new ApiError(res.status, message, body);
60
+ }
61
+ return text;
62
+ }
63
+ // --- carriers / configs ---
64
+ listCarriers(params = {}) {
65
+ return this.request(`/carriers${query({ withConfig: params.withConfig ? 'true' : undefined })}`);
66
+ }
67
+ getCarrier(carrierId) {
68
+ return this.request(`/carriers/${carrierId}`);
69
+ }
70
+ listConfigs(carrierId) {
71
+ return this.request(`/carriers/${carrierId}/configs`);
72
+ }
73
+ getConfigVersion(carrierId, version) {
74
+ return this.request(`/carriers/${carrierId}/configs/${version}`);
75
+ }
76
+ /** Create an account-scoped carrier config override. */
77
+ createConfig(carrierId, config) {
78
+ return this.request(`/carriers/${carrierId}/configs`, {
79
+ method: 'POST',
80
+ body: JSON.stringify(config),
81
+ });
82
+ }
83
+ /** Dry-run a config against a sample file (maps + previews, persists nothing). */
84
+ testConfig(carrierId, config, file) {
85
+ const form = new FormData();
86
+ form.set('config', JSON.stringify(config));
87
+ form.set('file', file);
88
+ return this.request(`/carriers/${carrierId}/configs/test`, { method: 'POST', body: form });
89
+ }
90
+ /** Infer a draft config from a sample file. */
91
+ inferConfig(carrierId, file, opts = {}) {
92
+ const form = new FormData();
93
+ form.set('file', file);
94
+ if (opts.sheetName)
95
+ form.set('sheetName', opts.sheetName);
96
+ return this.request(`/carriers/${carrierId}/configs/infer`, {
97
+ method: 'POST',
98
+ body: form,
99
+ });
100
+ }
101
+ // --- files ---
102
+ async uploadFile(input) {
103
+ const form = new FormData();
104
+ form.set('file', input.file);
105
+ form.set('carrierId', input.carrierId);
106
+ form.set('periodYear', String(input.periodYear));
107
+ form.set('periodMonth', String(input.periodMonth));
108
+ if (input.webhookUrl)
109
+ form.set('webhookUrl', input.webhookUrl);
110
+ if (input.replace)
111
+ form.set('replace', 'true');
112
+ const headers = input.idempotencyKey ? { 'idempotency-key': input.idempotencyKey } : undefined;
113
+ return this.request('/files', { method: 'POST', body: form, headers });
114
+ }
115
+ listFiles(params = {}) {
116
+ return this.request(`/files${query(params)}`);
117
+ }
118
+ getFile(fileId) {
119
+ return this.request(`/files/${fileId}`);
120
+ }
121
+ /**
122
+ * Re-process (re-score) a file's period without re-uploading — recomputes
123
+ * statuses/deltas against the current baseline. Use after uploading an earlier
124
+ * month out of order (see `FileSummary.rescoreSuggested`).
125
+ */
126
+ rescoreFile(fileId) {
127
+ return this.request(`/files/${fileId}/rescore`, { method: 'POST' });
128
+ }
129
+ /**
130
+ * Retract (unapply) a file's carrier+period — deletes the period's data with no
131
+ * re-upload and re-scores the following month. Scoped to the whole carrier+period
132
+ * (all files for it), so a period split across files clears as a unit. Returns
133
+ * `409` (`already_retracted`) if the file was already retracted/replaced.
134
+ */
135
+ retractFile(fileId) {
136
+ return this.request(`/files/${fileId}`, { method: 'DELETE' });
137
+ }
138
+ /**
139
+ * Purge the raw statement bytes from object storage (data retention). The file
140
+ * row + scored results remain; the file can no longer be re-ingested. Idempotent.
141
+ */
142
+ purgeFile(fileId) {
143
+ return this.request(`/files/${fileId}/purge`, {
144
+ method: 'POST',
145
+ });
146
+ }
147
+ // --- jobs ---
148
+ listJobs(params = {}) {
149
+ return this.request(`/jobs${query(params)}`);
150
+ }
151
+ getJob(jobId) {
152
+ return this.request(`/jobs/${jobId}`);
153
+ }
154
+ /** Download the exception file (rejected rows + their errors) for a job as CSV
155
+ * text. Retained for 30 days; a purged or absent file throws `ApiError` (404). */
156
+ downloadExceptions(jobId) {
157
+ return this.requestText(`/jobs/${jobId}/exceptions`);
158
+ }
159
+ getJobResults(jobId, params = {}) {
160
+ const { owedOnly, chargeback, ...rest } = params;
161
+ return this.request(`/jobs/${jobId}/results${query({
162
+ ...rest,
163
+ owedOnly: owedOnly ? 'true' : undefined,
164
+ chargeback: chargeback ? 'true' : undefined,
165
+ })}`);
166
+ }
167
+ getJobDeltas(jobId, params = {}) {
168
+ return this.request(`/jobs/${jobId}/deltas${query(params)}`);
169
+ }
170
+ retryJob(jobId) {
171
+ return this.request(`/jobs/${jobId}/retry`, {
172
+ method: 'POST',
173
+ });
174
+ }
175
+ // --- members ---
176
+ listMembers(params = {}) {
177
+ return this.request(`/members${query(params)}`);
178
+ }
179
+ getMember(memberRefId) {
180
+ return this.request(`/members/${memberRefId}`);
181
+ }
182
+ getMemberTimeline(memberRefId) {
183
+ return this.request(`/members/${memberRefId}/timeline`);
184
+ }
185
+ /** Full audit journey of a member: every period it appeared, the source file,
186
+ * commission/premium, status + flags, and field-level changes — in order. */
187
+ getMemberJourney(memberRefId) {
188
+ return this.request(`/members/${memberRefId}/journey`);
189
+ }
190
+ /** Full audit journey of a single policy (member-scoped). */
191
+ getPolicyJourney(policyRefId) {
192
+ return this.request(`/policies/${policyRefId}/journey`);
193
+ }
194
+ /** Where/when a member was last seen (period + originating file). */
195
+ getMemberLastSeen(memberRefId) {
196
+ return this.request(`/members/${memberRefId}/last-seen`);
197
+ }
198
+ // --- team ---
199
+ /** List the account's members. */
200
+ listTeam() {
201
+ return this.request('/team');
202
+ }
203
+ /** Invite a teammate by email (passwordless — they sign in with a one-time code). */
204
+ inviteTeammate(email) {
205
+ return this.request('/team/invites', {
206
+ method: 'POST',
207
+ body: JSON.stringify({ email }),
208
+ });
209
+ }
210
+ /** Remove a teammate and revoke their sessions. */
211
+ removeTeammate(userId) {
212
+ return this.request(`/team/${userId}`, { method: 'DELETE' });
213
+ }
214
+ // --- audit ---
215
+ /** Read the account's audit trail (newest first). Filter by `action`. */
216
+ listAudit(params = {}) {
217
+ return this.request(`/audit${query(params)}`);
218
+ }
219
+ // --- comparisons / reports ---
220
+ compare(params) {
221
+ return this.request(`/comparisons${query(params)}`);
222
+ }
223
+ rollup(period, carrierId) {
224
+ return this.request(`/reports/rollup${query({ period, carrierId })}`);
225
+ }
226
+ /** Chargebacks for a period, each enriched with the policy's original payout. */
227
+ listChargebacks(params = {}) {
228
+ return this.request(`/chargebacks${query(params)}`);
229
+ }
230
+ attrition(period, carrierId) {
231
+ return this.request(`/reports/attrition${query({ period, carrierId })}`);
232
+ }
233
+ attritionSeries(params = {}) {
234
+ return this.request(`/reports/attrition-series${query(params)}`);
235
+ }
236
+ dataQuality(period) {
237
+ return this.request(`/reports/data-quality${query({ period })}`);
238
+ }
239
+ // --- expected commission rates (the "owed" model inputs) ---
240
+ listExpectedRates(carrierId) {
241
+ return this.request(`/expected-rates${query({ carrierId })}`);
242
+ }
243
+ /** Upsert the contracted rate for a carrier (+ optional plan). Re-posting the
244
+ * same carrier+plan updates it. `rateValue` is a fraction for percent_of_premium.
245
+ * Returns `rescoredPeriods`: how many already-processed periods were re-scored to
246
+ * bring their stored "commission owed" totals in line with the new rate. */
247
+ upsertExpectedRate(input) {
248
+ return this.request('/expected-rates', {
249
+ method: 'POST',
250
+ body: JSON.stringify(input),
251
+ });
252
+ }
253
+ deleteExpectedRate(id) {
254
+ return this.request(`/expected-rates/${id}`, { method: 'DELETE' });
255
+ }
256
+ // --- webhooks ---
257
+ listWebhooks() {
258
+ return this.request('/webhooks');
259
+ }
260
+ /** Subscribe to job events. The signing `secret` is returned ONCE on creation. */
261
+ createWebhook(input) {
262
+ return this.request('/webhooks', {
263
+ method: 'POST',
264
+ body: JSON.stringify(input),
265
+ });
266
+ }
267
+ deleteWebhook(id) {
268
+ return this.request(`/webhooks/${id}`, { method: 'DELETE' });
269
+ }
270
+ // --- session / service ---
271
+ /** The account behind the current token. */
272
+ me() {
273
+ return this.request('/me');
274
+ }
275
+ /** Liveness probe (no auth required). */
276
+ health() {
277
+ return this.request('/health');
278
+ }
279
+ // --- billing / profile ---
280
+ getBilling() {
281
+ return this.request('/billing');
282
+ }
283
+ updateBilling(body) {
284
+ return this.request('/billing', { method: 'PUT', body: JSON.stringify(body) });
285
+ }
286
+ billingPreview() {
287
+ return this.request('/billing/preview');
288
+ }
289
+ createSetupIntent() {
290
+ return this.request('/billing/setup-intent', { method: 'POST' });
291
+ }
292
+ savePaymentMethod(paymentMethodId) {
293
+ return this.request('/billing/payment-method', { method: 'POST', body: JSON.stringify({ paymentMethodId }) });
294
+ }
295
+ // --- admin (role=admin session required) ---
296
+ admin = {
297
+ listAccounts: (status) => this.request(`/admin/accounts${query({ status })}`),
298
+ setBillingRate: (accountId, rateCents) => this.request(`/admin/accounts/${accountId}/billing-rate`, { method: 'PUT', body: JSON.stringify({ rateCents }) }),
299
+ getAccountBilling: (accountId) => this.request(`/admin/accounts/${accountId}/billing`),
300
+ /** Per-account dashboard: counts + latest-period rollup (members, status mix,
301
+ * owed, at-risk, chargebacks). */
302
+ accountOverview: (accountId) => this.request(`/admin/accounts/${accountId}/overview`),
303
+ accountFiles: (accountId, params = {}) => this.request(`/admin/accounts/${accountId}/files${query(params)}`),
304
+ accountJobs: (accountId, params = {}) => this.request(`/admin/accounts/${accountId}/jobs${query(params)}`),
305
+ accountUsers: (accountId) => this.request(`/admin/accounts/${accountId}/users`),
306
+ /** Recent scheduled-maintenance (cron) runs + the task schedule — System tab. */
307
+ cronRuns: (params = {}) => this.request(`/admin/system/cron-runs${query(params)}`),
308
+ /** Platform billing/revenue summary — heartbeats, projected revenue, A/R. */
309
+ revenue: () => this.request('/admin/revenue'),
310
+ /** Set an account's AI-assistant monthly cap (cents) and pass-through billing. */
311
+ setAiSettings: (accountId, settings) => this.request(`/admin/accounts/${accountId}/ai-settings`, { method: 'PUT', body: JSON.stringify(settings) }),
312
+ setSurcharge: (accountId, enabled) => this.request(`/admin/accounts/${accountId}/surcharge`, { method: 'PUT', body: JSON.stringify({ enabled }) }),
313
+ approveAccount: (accountId) => this.request(`/admin/accounts/${accountId}/approve`, { method: 'POST' }),
314
+ /** Provision (or re-provision) an account's data store. Pass a connString to
315
+ * use a database created out of band; omit it to auto-create a Neon DB. */
316
+ provisionAccount: (accountId, connString) => this.request(`/admin/accounts/${accountId}/provision`, {
317
+ method: 'POST',
318
+ body: JSON.stringify(connString ? { connString } : {}),
319
+ }),
320
+ createAccount: (name) => this.request('/admin/accounts', {
321
+ method: 'POST',
322
+ body: JSON.stringify({ name }),
323
+ }),
324
+ /** Purge ALL raw statement bytes for an account from object storage (retention). */
325
+ purgeAccountFiles: (accountId) => this.request(`/admin/accounts/${accountId}/purge-files`, { method: 'POST' }),
326
+ issueToken: (accountId, label) => this.request(`/admin/accounts/${accountId}/tokens`, { method: 'POST', body: JSON.stringify({ label }) }),
327
+ /** List an account's API tokens — metadata only (never the secret). */
328
+ listTokens: (accountId) => this.request(`/admin/accounts/${accountId}/tokens`),
329
+ revokeToken: (tokenId) => this.request(`/admin/tokens/${tokenId}/revoke`, {
330
+ method: 'POST',
331
+ }),
332
+ storeCredentials: (accountId, body) => this.request(`/admin/accounts/${accountId}/credentials`, { method: 'PUT', body: JSON.stringify(body) }),
333
+ createCarrier: (name, slug) => this.request('/admin/carriers', {
334
+ method: 'POST',
335
+ body: JSON.stringify({ name, slug }),
336
+ }),
337
+ renameCarrier: (id, name, slug) => this.request(`/admin/carriers/${id}`, {
338
+ method: 'PUT',
339
+ body: JSON.stringify({ name, ...(slug ? { slug } : {}) }),
340
+ }),
341
+ /** Onboarding: infer a draft config from a sample statement (CSV/XLSX). */
342
+ inferConfig: (carrierId, file, opts = {}) => {
343
+ const form = new FormData();
344
+ form.append('file', file);
345
+ if (opts.sheetName)
346
+ form.append('sheetName', opts.sheetName);
347
+ if (opts.fileType)
348
+ form.append('fileType', opts.fileType);
349
+ return this.request(`/admin/carriers/${carrierId}/configs/infer`, {
350
+ method: 'POST',
351
+ body: form,
352
+ });
353
+ },
354
+ listUsers: () => this.request('/admin/users'),
355
+ createUser: (body) => this.request('/admin/users', { method: 'POST', body: JSON.stringify(body) }),
356
+ /** Update a user's role. A user's account link is immutable (set at invite). */
357
+ updateUser: (id, body) => this.request(`/admin/users/${id}`, {
358
+ method: 'PUT',
359
+ body: JSON.stringify(body),
360
+ }),
361
+ deleteUser: (id) => this.request(`/admin/users/${id}`, { method: 'DELETE' }),
362
+ createGlobalConfig: (carrierId, config) => this.request(`/admin/carriers/${carrierId}/configs`, {
363
+ method: 'POST',
364
+ body: JSON.stringify(config),
365
+ }),
366
+ listCarrierConfigs: (carrierId) => this.request(`/admin/carriers/${carrierId}/configs`),
367
+ updateCarrierConfig: (carrierId, configId, config) => this.request(`/admin/carriers/${carrierId}/configs/${configId}`, { method: 'PUT', body: JSON.stringify(config) }),
368
+ addAllowlist: (email) => this.request('/admin/allowlist', {
369
+ method: 'POST',
370
+ body: JSON.stringify({ email }),
371
+ }),
372
+ listJobs: (params = {}) => this.request(`/admin/jobs${query(params)}`),
373
+ jobDetail: (jobId) => this.request(`/admin/jobs/${jobId}`),
374
+ retryJob: (jobId) => this.request(`/admin/jobs/${jobId}/retry`, {
375
+ method: 'POST',
376
+ }),
377
+ rescoreJob: (jobId) => this.request(`/admin/jobs/${jobId}/rescore`, { method: 'POST' }),
378
+ metrics: () => this.request('/admin/metrics'),
379
+ logs: (params = {}) => this.request(`/admin/logs${query(params)}`),
380
+ };
381
+ }
382
+ export function query(params) {
383
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== '');
384
+ if (entries.length === 0)
385
+ return '';
386
+ const sp = new URLSearchParams();
387
+ for (const [k, v] of entries)
388
+ sp.set(k, String(v));
389
+ return `?${sp.toString()}`;
390
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@commissionsight/sdk",
3
+ "version": "2.0.0",
4
+ "description": "Lightweight, zero-dependency TypeScript client for the CommissionSight API.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/commissionsight/sdk.git"
19
+ },
20
+ "keywords": [
21
+ "commissionsight",
22
+ "commission",
23
+ "insurance",
24
+ "carrier",
25
+ "api-client",
26
+ "sdk",
27
+ "typescript"
28
+ ],
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ]
34
+ }