@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/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/index.d.ts +977 -0
- package/dist/index.js +390 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CommissionSight
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# @commissionsight/sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@commissionsight/sdk)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
A lightweight, **zero-dependency** TypeScript client for the [CommissionSight](https://commissionsight.com) API.
|
|
7
|
+
|
|
8
|
+
CommissionSight ingests carrier commission statements (CSV/XLSX), normalizes them across carriers, and scores every member period-over-period as **🟢 green / 🟡 yellow / 🔴 red** with explicit change flags — so you can see new business, commission changes, and attrition at a glance. This SDK wraps the REST API with full type definitions.
|
|
9
|
+
|
|
10
|
+
- **Zero runtime dependencies** — just `fetch`.
|
|
11
|
+
- **Fully typed** — every request and response is described by an exported interface.
|
|
12
|
+
- **ESM-only** — modern `import` syntax (Node 18+, Bun, Deno, browsers, Cloudflare Workers).
|
|
13
|
+
- **Isomorphic** — pass your own `fetch` for non-standard runtimes or tests.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @commissionsight/sdk
|
|
21
|
+
# or
|
|
22
|
+
bun add @commissionsight/sdk
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @commissionsight/sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> **ESM-only.** This package ships `type: "module"` and only exposes an `import` entry point. Use `import`, not `require()`. Requires a runtime with a global `fetch` (Node 18+).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { CommissionSightClient } from '@commissionsight/sdk';
|
|
35
|
+
|
|
36
|
+
const cs = new CommissionSightClient({
|
|
37
|
+
baseUrl: 'https://api.commissionsight.com/v1',
|
|
38
|
+
token: process.env.COMMISSIONSIGHT_TOKEN, // a per-account API token
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const carriers = await cs.listCarriers();
|
|
42
|
+
console.log(carriers.data); // [{ id, name, slug }, ...]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Client options
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
interface ClientOptions {
|
|
49
|
+
baseUrl: string; // e.g. https://api.commissionsight.com/v1
|
|
50
|
+
token?: string; // Bearer token; can also be set later via setToken()
|
|
51
|
+
fetch?: typeof fetch; // custom fetch (defaults to globalThis.fetch)
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`baseUrl` may include or omit a trailing slash — it's normalized. Set or rotate the token at any time:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
cs.setToken(newToken);
|
|
59
|
+
cs.setToken(undefined); // clear it
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Authentication
|
|
65
|
+
|
|
66
|
+
The SDK is for **server-to-server integrations**, authenticated with a **per-account API token**
|
|
67
|
+
issued to you by CommissionSight. Pass it as `token` when constructing the client (or set/rotate it
|
|
68
|
+
later); every request is sent as `Authorization: Bearer <token>`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const cs = new CommissionSightClient({
|
|
72
|
+
baseUrl: 'https://api.commissionsight.com/v1',
|
|
73
|
+
token: process.env.COMMISSIONSIGHT_TOKEN, // your per-account API token
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
cs.setToken(newToken); // rotate at any time
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Uploading a statement & tracking the job
|
|
82
|
+
|
|
83
|
+
Uploading a file kicks off an asynchronous ingest **job**. Poll the job until it's `completed`, then read the scored results.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// `file` is a File or Blob — e.g. from an <input type="file"> or fs in Node.
|
|
87
|
+
const { jobId, fileId } = await cs.uploadFile({
|
|
88
|
+
file,
|
|
89
|
+
carrierId: 'car_123',
|
|
90
|
+
periodYear: 2026,
|
|
91
|
+
periodMonth: 5,
|
|
92
|
+
// Optional: get a signed webhook callback when the job finishes.
|
|
93
|
+
webhookUrl: 'https://acme.com/hooks/commissionsight',
|
|
94
|
+
// Optional: safe retries — re-uploading with the same key won't double-ingest.
|
|
95
|
+
idempotencyKey: 'acme-2026-05-aetna',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Poll until done.
|
|
99
|
+
let job = await cs.getJob(jobId);
|
|
100
|
+
while (job.status === 'queued' || job.status === 'processing') {
|
|
101
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
102
|
+
job = await cs.getJob(jobId);
|
|
103
|
+
}
|
|
104
|
+
if (job.status === 'failed') throw new Error(job.error ?? 'ingest failed');
|
|
105
|
+
|
|
106
|
+
// Read the scored rows for this period.
|
|
107
|
+
const results = await cs.getJobResults(jobId, { status: 'yellow' });
|
|
108
|
+
for (const row of results.data) {
|
|
109
|
+
console.log(row.memberRefId, row.status, row.flags, row.commissionAmount);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Re-scoring after an out-of-order upload
|
|
114
|
+
|
|
115
|
+
If you upload an earlier month *after* a later one, the later period's scoring becomes stale. `listFiles()` flags this with `rescoreSuggested`; refresh it without re-uploading:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const files = await cs.listFiles({ carrierId: 'car_123' });
|
|
119
|
+
for (const f of files.data) {
|
|
120
|
+
if (f.rescoreSuggested) await cs.rescoreFile(f.id);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Correcting or removing a statement
|
|
125
|
+
|
|
126
|
+
Uploading over a carrier+period that already has a file fails with `409` (`period_exists`) — re-uploads are never silently destructive. To apply a **corrected file**, pass `replace: true`: the existing data is retracted and the corrected file re-ingested atomically, so members the correction drops leave no orphan rows. The following month is re-scored automatically.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// Carrier sent a corrected April file — replace the one on record.
|
|
130
|
+
const { jobId, mode } = await cs.uploadFile({
|
|
131
|
+
file: correctedFile,
|
|
132
|
+
carrierId: 'car_123',
|
|
133
|
+
periodYear: 2026,
|
|
134
|
+
periodMonth: 4,
|
|
135
|
+
replace: true, // omit → 409 period_exists if the period already exists
|
|
136
|
+
});
|
|
137
|
+
// mode === 'replace'
|
|
138
|
+
|
|
139
|
+
// Or remove a period entirely (no re-upload), re-scoring the next month:
|
|
140
|
+
await cs.retractFile(fileId);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Status & flags
|
|
146
|
+
|
|
147
|
+
Every scored member row carries a `status` and zero or more `flags`:
|
|
148
|
+
|
|
149
|
+
| `status` | Meaning |
|
|
150
|
+
| --------- | ------- |
|
|
151
|
+
| 🟢 `green` | Present and unchanged vs. the prior period. |
|
|
152
|
+
| 🟡 `yellow` | Present but something tracked changed (see flags). |
|
|
153
|
+
| 🔴 `red` | Present in the prior period, **absent now** (dropped). |
|
|
154
|
+
|
|
155
|
+
| `flag` | Meaning |
|
|
156
|
+
| ------------------------- | ------- |
|
|
157
|
+
| `NEW` | First time this member is seen. |
|
|
158
|
+
| `COMMISSION_CHANGED` | Commission amount differs from the prior period. |
|
|
159
|
+
| `DATA_CHANGED` | A tracked non-commission field changed. |
|
|
160
|
+
| `DROPPED` | Was present before, missing now. |
|
|
161
|
+
| `REAPPEARED` | Returned after being absent. |
|
|
162
|
+
| `REAPPEARED_WITH_DELTA` | Returned **and** came back with a different commission. |
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import type { Status, Flag, ResultRow } from '@commissionsight/sdk';
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Reading data
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// Files & jobs
|
|
174
|
+
await cs.listFiles({ carrierId, limit: 50 });
|
|
175
|
+
await cs.listJobs({ status: 'completed' });
|
|
176
|
+
await cs.getJobResults(jobId, { status: 'red', limit: 100, offset: 0 });
|
|
177
|
+
await cs.getJobDeltas(jobId, { changeType: 'COMMISSION_CHANGED' });
|
|
178
|
+
await cs.retryJob(jobId);
|
|
179
|
+
|
|
180
|
+
// Members & policies — status, timeline, and the full audit journey
|
|
181
|
+
await cs.listMembers({ carrierId, status: 'yellow' });
|
|
182
|
+
await cs.getMemberTimeline(memberRefId);
|
|
183
|
+
await cs.getMemberJourney(memberRefId); // every period, source file, status + field changes
|
|
184
|
+
await cs.getPolicyJourney(policyRefId);
|
|
185
|
+
|
|
186
|
+
// Rejected rows from an ingest (exception file, as CSV text)
|
|
187
|
+
const csv = await cs.downloadExceptions(jobId);
|
|
188
|
+
|
|
189
|
+
// Carriers & their mapping configs
|
|
190
|
+
await cs.listCarriers({ withConfig: true });
|
|
191
|
+
await cs.listConfigs(carrierId);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Commission owed (expected vs. actual)
|
|
195
|
+
|
|
196
|
+
Set your contracted rate per carrier; the API computes the recoverable shortfall vs. what the
|
|
197
|
+
carrier actually paid (it's also a per-member field on the results grid).
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
await cs.upsertExpectedRate({ carrierId, rateType: 'percent_of_premium', rateValue: 0.2 });
|
|
201
|
+
const rollup = await cs.rollup('2026-05', carrierId);
|
|
202
|
+
console.log(rollup.totals.commissionOwed, rollup.totals.owedEvaluated, rollup.totals.owedTotal);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Chargebacks
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const cb = await cs.listChargebacks({ carrierId }); // negative-commission events + original payout
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Webhooks
|
|
212
|
+
|
|
213
|
+
Subscribe to signed callbacks instead of polling:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
await cs.createWebhook({ url: 'https://acme.com/hooks/cs', events: ['job.completed'] });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Audit trail
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
await cs.listAudit({ limit: 50 }); // append-only record of account actions
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Compare any two periods
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const cmp = await cs.compare({ from: '2026-04', to: '2026-05', carrierId });
|
|
229
|
+
console.log(cmp.summary); // { green, yellow, red, new, reappeared, total }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Reports
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
await cs.rollup('2026-05', carrierId); // period totals by status + by carrier
|
|
236
|
+
await cs.attrition('2026-05', carrierId); // attrition rate for a period
|
|
237
|
+
await cs.attritionSeries({ months: 12 }); // attrition trend
|
|
238
|
+
await cs.dataQuality('2026-05'); // statement-quality signals (ok/watch/alert)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Pagination
|
|
244
|
+
|
|
245
|
+
List endpoints return a `Page<T>`:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
interface Page<T> {
|
|
249
|
+
data: T[];
|
|
250
|
+
pagination?: {
|
|
251
|
+
limit: number;
|
|
252
|
+
offset?: number;
|
|
253
|
+
nextCursor?: number | null;
|
|
254
|
+
hasMore: boolean;
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Offset-based endpoints accept `{ limit, offset }`; cursor-based ones (e.g. `listFiles`) accept `{ limit, cursor }` and return `nextCursor`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Error handling
|
|
264
|
+
|
|
265
|
+
Any non-2xx response throws an `ApiError`. It carries the HTTP status and the parsed [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) `problem+json` body when present.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { ApiError } from '@commissionsight/sdk';
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await cs.getJob('does-not-exist');
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (err instanceof ApiError) {
|
|
274
|
+
console.error(err.status); // e.g. 404
|
|
275
|
+
console.error(err.message); // problem `title`
|
|
276
|
+
console.error(err.body); // full problem+json payload
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## TypeScript
|
|
284
|
+
|
|
285
|
+
Every payload is exported as a named type — `ResultRow`, `ComparisonRow`, `JobSummary`, `FileSummary`, `Journey`, `ChargebackRow`, `ExpectedCommissionRate`, `AuditEvent`, `DataQualityReport`, `AttritionPoint`, and the `Status` / `Flag` unions. Import what you need:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import type {
|
|
289
|
+
CommissionSightClient,
|
|
290
|
+
ResultRow,
|
|
291
|
+
JobSummary,
|
|
292
|
+
Journey,
|
|
293
|
+
Status,
|
|
294
|
+
Flag,
|
|
295
|
+
} from '@commissionsight/sdk';
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Links
|
|
301
|
+
|
|
302
|
+
- **Website:** https://commissionsight.com
|
|
303
|
+
- **API docs:** https://docs.commissionsight.com
|
|
304
|
+
- **Issues:** https://github.com/commissionsight/sdk/issues
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
[MIT](./LICENSE) © CommissionSight
|