@dossier-wiki/os-sdk 0.7.0 → 1.4.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/docs/REFERENCE.md CHANGED
@@ -16,6 +16,12 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
16
16
 
17
17
  ## Resource map
18
18
 
19
+ ### `sessions` and `channelEndpoints`
20
+
21
+ Both expose `list()`, `get(id)`, and `update(id, input)`. Creation is owned by
22
+ transaction ingestion resolution. Timestamp updates widen, rather than narrow,
23
+ the recorded event-time bounds.
24
+
19
25
  ### `auth`
20
26
 
21
27
  | Method | Endpoint |
@@ -34,11 +40,24 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
34
40
  | `get(customerId, query?)` | `GET /v1/customers/individuals/{customerId}` |
35
41
  | `update(customerId, input)` | `PATCH /v1/customers/individuals/{customerId}` |
36
42
 
43
+ `create(input)` requires `externalId` and `fullName`. Optional create fields are
44
+ `status`, `preferredName`, `aliases`, `dateOfBirth`, `nationality[]`,
45
+ `countryOfResidence[]`, `taxId`, `emails[]`, `phones[]`,
46
+ `identificationDocuments[]`, `faceImageFileIds[]`, `addresses[]`, `tags[]`,
47
+ `assignedToUserIds[]`, and `assignedToTeamIds[]`. Contact entries are
48
+ `{ value, primary }`.
49
+
50
+ Legacy field names from the pre-1.0 contract are still accepted on create and
51
+ map onto the canonical ones — `email` / `phone` scalars fold into `emails[]` /
52
+ `phones[]`, an identification document's `issuingCountry` folds into `country`,
53
+ and `createdAt` backfills the creation timestamp. The canonical field wins
54
+ whenever both are supplied. Prefer the canonical names in new integrations.
55
+
37
56
  `individuals.identificationDocuments`
38
57
 
39
58
  | Method | Endpoint |
40
59
  | --- | --- |
41
- | `add(customerId, input)` | `POST .../identification-documents` |
60
+ | `create(customerId, input)` | `POST .../identification-documents` |
42
61
  | `update(customerId, documentId, input)` | `PATCH .../identification-documents/{documentId}` |
43
62
  | `delete(customerId, documentId)` | `DELETE .../identification-documents/{documentId}` |
44
63
 
@@ -59,6 +78,20 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
59
78
  | `get(customerId, query?)` | `GET /v1/customers/entities/{customerId}` |
60
79
  | `update(customerId, input)` | `PATCH /v1/customers/entities/{customerId}` |
61
80
 
81
+ `create(input)` requires `externalId` and `legalName`. Optional create fields
82
+ are `status`, `tradingNames[]`, `registrationNumber`, `taxId`,
83
+ `countryOfIncorporation`, `dateOfIncorporation`, `entityType`, `websites[]`,
84
+ `stakeholders[]`, `locations[]`, `tags[]`, `assignedToUserIds[]`, and
85
+ `assignedToTeamIds[]`. Each stakeholder has required `name` and uppercase
86
+ `role` (`SHAREHOLDER`, `UBO`, or `DIRECTOR`). Entity locations use the same
87
+ `AddressInputDto` shape as individual addresses.
88
+
89
+ Migration: prefer individual `fullName`, contact-entry arrays, and country-code
90
+ arrays; prefer entity `tradingNames` and `locations`. The older create-time
91
+ `createdAt`, scalar `email` / `phone`, entity `addresses`, and entity
92
+ `parentEntityId` remain accepted as deprecated aliases so existing integrations
93
+ keep working — the canonical field wins whenever both are supplied.
94
+
62
95
  `entities.stakeholders`
63
96
 
64
97
  | Method | Endpoint |
@@ -87,8 +120,30 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
87
120
  | Method | Endpoint |
88
121
  | --- | --- |
89
122
  | `trigger(customerId)` | `POST .../screen` |
123
+ | `listHistory(customerId, { limit? })` | `GET .../screening-history` |
124
+ | `getLast(customerId)` | `GET .../screening-history/latest` |
125
+ | `listResults(customerId, { disposition? })` | `GET .../screening-results` |
90
126
  | `listConfirmedMatches(customerId)` | `GET .../confirmed-matches` |
91
127
 
128
+ `listResults` returns every match for the customer (`ScreeningMatchResultDto[]`) with its current disposition — true positives (`CONFIRMED`), false positives (`FALSE_POSITIVE`) and unresolved (`PENDING`). Pass `disposition` to filter. `listConfirmedMatches` is the `CONFIRMED`-only alias.
129
+
130
+ Every result carries `tagline` as a string: the provider's short role/bio line for `HIGH_RISK` (PEP) matches, and `""` for every other match type or where no tagline exists. It is never omitted or `null`.
131
+
132
+ **Not every `FALSE_POSITIVE` was set by a human.** Where the provider supports it, every match is compared against the customer's date of birth, geography, role timeline and entity kind; one that is positively contradicted is dispositioned `FALSE_POSITIVE` automatically, and an alert whose every match is eliminated this way closes itself. The verdict is on `disambiguation` (`MatchDisambiguationDto`):
133
+
134
+ ```ts
135
+ const matches = await os.customers.screening.listResults(customerId);
136
+
137
+ for (const m of matches.filter((m) => m.disposition === 'FALSE_POSITIVE')) {
138
+ if (m.disambiguation?.decision === 'discard') {
139
+ // Cleared by the provider, not by one of your users.
140
+ console.log(m.matchedName, m.disambiguation.reasonCodes, m.disambiguation.rationale);
141
+ }
142
+ }
143
+ ```
144
+
145
+ The assessment only ever eliminates on a contradiction — never on a missing attribute, never on geography alone, and never on a sanctions listing unless the contradiction is hard. A `retain` is not a confirmation, only the absence of grounds to eliminate, and an absent `disambiguation` means the match was never assessed. `resolvedBy` distinguishes the automation from your users.
146
+
92
147
  `customers.riskRating` — `update(customerId, input)` → `PUT .../risk-rating`
93
148
 
94
149
  `customers.riskAssessments` — `list`, `create` on `.../risk-assessments`
@@ -103,6 +158,87 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
103
158
  | `register(customerId, input)` | `POST .../devices` |
104
159
  | `updateTrust(customerId, deviceId, input)` | `PUT .../devices/{deviceId}/trust` |
105
160
 
161
+ Registration and transaction ingestion share one canonical identity path. Send
162
+ one or more raw identities; the server converts each to a tenant-bound protected
163
+ key in memory, links the resulting canonical Device, and never persists or
164
+ returns the raw values:
165
+
166
+ ```ts
167
+ const device = await os.customers.devices.register(customerId, {
168
+ fingerprints: [
169
+ { value: browserVisitorId },
170
+ { value: mobileInstallationId, provider: 'MOBILE_SDK', type: 'INSTALLATION_ID' },
171
+ ],
172
+ browserDetails: { browserName: 'Chrome', os: 'ANDROID', device: 'MOBILE' },
173
+ });
174
+ ```
175
+
176
+ `list`, `register`, `updateTrust`, and `os.devices.get` return the same safe
177
+ `DeviceResponseDto`. It excludes raw fingerprints, protected hashes, and raw IP
178
+ addresses; temporal network and location evidence is exposed through dedicated
179
+ observation contracts.
180
+
181
+ `customers.assignment`
182
+
183
+ | Method | Endpoint |
184
+ | --- | --- |
185
+ | `assign(customerId, targets)` | `POST /v1/customers/{customerId}/assignment` |
186
+ | `unassign(customerId, targets)` | `DELETE /v1/customers/{customerId}/assignment` |
187
+ | `assignMany({ customerIds, ...targets })` | `POST /v1/customers/assignment` |
188
+
189
+ Standing assignment can also be stamped at create via `assignedToUserIds` / `assignedToTeamIds` on `individuals.create` / `entities.create`.
190
+
191
+ `individuals.create(input, { screening: 'sync' })` / `entities.create(input, { screening: 'sync' })` run the INITIAL screening inline and return it under `screening` (`InitialScreeningResultDto`: run status, `alertId`, and the `matches` array in the `listResults` shape). Without the option the call queues the screening as before and the response has no `screening` field.
192
+
193
+ `targets` must contain at least one non-empty `userIds` or `teamIds` array.
194
+ Bulk assignment requires at least one `customerId` and validates the complete
195
+ request before writing: an unknown or archived customer, or an invalid target,
196
+ rejects the whole request instead of returning partial success. Assignment
197
+ writes require `customer:update`; archived customers return 422.
198
+
199
+ ### `identificationDocuments` (document-scoped)
200
+
201
+ Top-level resource for operating on a single identification document by its id
202
+ — the review workflow's surface. Customer-scoped create/update/delete also
203
+ exist on `individuals.identificationDocuments` / `entities.identificationDocuments`.
204
+
205
+ | Method | Endpoint |
206
+ | --- | --- |
207
+ | `get(id)` | `GET /v1/identification-documents/{id}` |
208
+ | `update(id, input)` | `PATCH /v1/identification-documents/{id}` |
209
+ | `transition(id, input)` | `POST /v1/identification-documents/{id}/transitions` |
210
+ | `archive(id, input)` | `POST /v1/identification-documents/{id}/archive` |
211
+ | `restore(id)` | `POST /v1/identification-documents/{id}/restore` |
212
+ | `delete(id)` | `DELETE /v1/identification-documents/{id}` |
213
+
214
+ `transition(id, input)` moves the review verdict (`PENDING → APPROVED /
215
+ REJECTED`); `rejectionNarrative` is required when `status = REJECTED`, and
216
+ `rejectionTags` optionally classify the rejection. `update(id, input)` corrects
217
+ captured document fields (name, number, DOB, addresses, …) — allowed while the
218
+ document is `PENDING`; captured fields on an `APPROVED` document are frozen
219
+ until it is transitioned back to `PENDING` (notes/tags/attachments stay
220
+ editable). Every update is activity-logged with a per-field `{field, from, to}`
221
+ diff. `comments` proxy: `identificationDocuments.comments`.
222
+
223
+ ### `teams`
224
+
225
+ | Method | Endpoint |
226
+ | --- | --- |
227
+ | `list({ search?, tags? })` | `GET /v1/teams` |
228
+ | `get(id)` | `GET /v1/teams/{id}` |
229
+ | `create(input)` | `POST /v1/teams` |
230
+ | `update(id, input)` | `PATCH /v1/teams/{id}` |
231
+ | `archive(id)` | `POST /v1/teams/{id}/archive` |
232
+ | `delete(id)` | `DELETE /v1/teams/{id}` |
233
+ | `addMembers(id, { userIds })` | `POST /v1/teams/{id}/members` |
234
+ | `removeMembers(id, { userIds })` | `DELETE /v1/teams/{id}/members` |
235
+
236
+ `list` / `get` are open to any authenticated caller. Every other method needs the `team:create` / `team:update` / `team:delete` org permissions, which only `owner` and `admin` hold. An API key inherits the role of the user who created it, so a key created by a `member` gets a 403 — make the key under an owner or admin account if it has to manage teams.
237
+
238
+ `archive(id)` keeps the team resolvable for history but takes it out of `list` and out of notification fan-out. Prefer it to `delete(id)`, which leaves historical assignment ids dangling as tombstones.
239
+
240
+ Teams carry catalogue-governed tags under the `team.tags` field-key. `create` and `update` accept `tags: TagValueInput[]`, and every read returns `tags: TagValue[]`. `update` replaces the whole set — send every tag the team should end up with, omit the field to leave the tags alone, or pass `[]` to clear them. `list({ tags: ['aml-review'] })` is an any-of filter: a team matches when it carries at least one of the given values.
241
+
106
242
  ### `accounts`
107
243
 
108
244
  | Method | Endpoint |
@@ -124,6 +260,7 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
124
260
  | `update(id, input)` | `PATCH /v1/counterparties/{id}` |
125
261
  | `link(id, input)` | `POST /v1/counterparties/{id}/link` |
126
262
  | `unlink(id, input)` | `POST /v1/counterparties/{id}/unlink` |
263
+ | `merge(id, input)` | `POST /v1/counterparties/{id}/merge` |
127
264
 
128
265
  `counterparties.riskAssessments` — `list`, `create` on `.../risk-assessments`
129
266
 
@@ -134,6 +271,26 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
134
271
  | Method | Endpoint |
135
272
  | --- | --- |
136
273
  | `get(deviceId)` | `GET /v1/devices/{deviceId}` |
274
+ | `merge(targetDeviceId, input)` | `POST /v1/devices/{targetDeviceId}/merge` |
275
+
276
+ `merge` makes the target canonical without deleting source history. Its required
277
+ `reason` and actor are retained in the immutable merge audit record.
278
+
279
+ ### `paymentInstruments`
280
+
281
+ | Method | Endpoint |
282
+ | --- | --- |
283
+ | `list()` | `GET /v1/payment-instruments` |
284
+ | `get(id)` | `GET /v1/payment-instruments/{id}` |
285
+ | `create(input)` | `POST /v1/payment-instruments` |
286
+ | `update(id, input)` | `PATCH /v1/payment-instruments/{id}` |
287
+ | `linkAccount(id, input)` | `POST /v1/payment-instruments/{id}/accounts` |
288
+ | `unlinkAccount(id, relationshipId, validTo?)` | `POST /v1/payment-instruments/{id}/accounts/{relationshipId}/unlink` |
289
+ | `linkCustomer(id, input)` | `POST /v1/payment-instruments/{id}/customers` |
290
+ | `unlinkCustomer(id, relationshipId, validTo?)` | `POST /v1/payment-instruments/{id}/customers/{relationshipId}/unlink` |
291
+
292
+ Account and Customer links are temporal relationships. Unlinking closes the
293
+ relationship at `validTo` (or the server's current time); it does not delete it.
137
294
 
138
295
  ### `transactions`
139
296
 
@@ -141,6 +298,89 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
141
298
  | --- | --- |
142
299
  | `ingest(input)` | `POST /v1/transactions` |
143
300
  | `patchStatus(input)` | `PATCH /v1/transactions` |
301
+ | `get(reference, { by? })` | `GET /v1/transactions/{transactionId}` — returns `TransactionResponseDto`; `by: 'id'` looks up the internal UUID |
302
+
303
+ `ingest` accepts canonical `paymentInstrument` identity by internal ID,
304
+ integration `externalId`, or typed fingerprint. Transaction resolution creates
305
+ a Payment Instrument on a deterministic miss. `transaction.channel` may include
306
+ an endpoint `identifier`, provider namespace, display name, physical location,
307
+ and bounded scalar `context`.
308
+
309
+ ### `transactionAttempts`
310
+
311
+ | Method | Endpoint |
312
+ | --- | --- |
313
+ | `ingest(input)` | `POST /v1/transaction-attempts` |
314
+ | `list(input?)` | `GET /v1/transaction-attempts` |
315
+ | `get(id)` | `GET /v1/transaction-attempts/{id}` |
316
+
317
+ `attemptId` is a tenant-scoped idempotency key. Reusing it with identical
318
+ content returns the existing record; reusing it with changed content returns
319
+ `409 ATTEMPT_IDEMPOTENCY_CONFLICT`. Attempts are behavioural history and do not
320
+ trigger rule evaluation or contribute to transaction financial analytics.
321
+ Its `paymentInstrument` resolution is lookup-only: a matching ID, external ID,
322
+ or protected typed fingerprint is linked to the attempt, while a miss remains
323
+ unresolved and never creates a Payment Instrument.
324
+
325
+ `list` supports opaque cursor pagination and safe filters for outcomes,
326
+ operation, observation time, resolved subjects, IIN, device, session, and
327
+ channel endpoint. Read projections exclude request hashes and protected
328
+ payment-instrument/IP lookup hashes.
329
+
330
+ ### `behaviorSettings`
331
+
332
+ | Method | Endpoint |
333
+ | --- | --- |
334
+ | `get()` | `GET /v1/config/behavior-settings` |
335
+ | `update(input)` | `PATCH /v1/config/behavior-settings` |
336
+
337
+ The partial update controls attempt retention (1–3650 days) and attempt coverage
338
+ (`ALL` or `SELECTIVE`). Existing attempt expiry timestamps are unchanged; the
339
+ new retention applies to later ingestion and cleanup remains explicit.
340
+
341
+ `alerts.attemptEvidence(alertId)` returns the immutable, bounded Transaction
342
+ Attempt facts captured when attempt history contributed to an alert. Evidence
343
+ retention is independent of source-attempt cleanup.
344
+
345
+ ### `alerts`
346
+
347
+ | Method | Endpoint |
348
+ | --- | --- |
349
+ | `create(input)` | `POST /v1/alerts` |
350
+ | `get(alertId)` | `GET /v1/alerts/{alertId}` |
351
+ | `updateNotes(alertId, { notes })` | `PATCH /v1/alerts/{alertId}/notes` |
352
+ | `resources.list(alertId)` | `GET /v1/alerts/{alertId}/resources` |
353
+ | `resources.add(alertId, { resourceType, resourceId })` | `POST /v1/alerts/{alertId}/resources` |
354
+ | `resources.remove(alertId, alertResourceId)` | `DELETE /v1/alerts/{alertId}/resources/{alertResourceId}` |
355
+ | `attemptEvidence(alertId)` | `GET /v1/alerts/{alertId}/attempt-evidence` |
356
+ | `comments.*` | `/v1/platform/v1/comments` (resourceType `ALERT`) |
357
+
358
+ `create` raises a **custom alert** by hand. `alertType` is optional: name one of
359
+ the organisation's Custom Alert Types (`config.customAlertTypes`) by its machine
360
+ name and `fields` are Tier A typed fields validated against its schema (unknown
361
+ keys rejected, required keys enforced, at creation only); omit it for a
362
+ free-form alert whose `fields` are self-declaring (Tier B). `riskLevel`
363
+ defaults to the type's `defaultRiskLevel`, or MEDIUM without a type. `assignedToUserIds` / `assignedToTeamIds` set at
364
+ creation notify the assignees. On tenants using Defined workflows the alert is
365
+ enrolled into the "Custom alerts" workflow (Initial review → Secondary review →
366
+ Resolved by default); otherwise it follows the simple Open/Closed process.
367
+
368
+ `externalRequestId` is an idempotency key scoped per **organization**, not per
369
+ API key: two integrations replaying the same upstream event converge on one
370
+ alert, and the replay returns the existing alert with `201`.
371
+
372
+ `resources` accept any registered resource type (`customer`, `account`,
373
+ `transaction`, `counterparty`, `alert`, `rule`, …); the key is case-insensitive
374
+ and the target must exist in the organisation. Links are idempotent on
375
+ `(resourceType, resourceId)`. `remove` takes the **link id** returned by
376
+ `list` / `add`.
377
+
378
+ `notes` is a single markdown narrative on any alert type (screening and
379
+ rule-trigger alerts included). Writes obey the same owner-only editing rule as
380
+ the rest of the alert on workflow alerts that restrict editing to owners.
381
+
382
+ These endpoints need the `alerts:read` / `alerts:write` API-key permissions
383
+ (`Alerts Read` / `Alerts Write` in Settings → Developer).
144
384
 
145
385
  ### `events`
146
386
 
@@ -148,6 +388,100 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
148
388
  | --- | --- |
149
389
  | `ingest(input)` | `POST /v1/events` |
150
390
 
391
+ Every Event is about exactly one resource, named as
392
+ `resource: { type, id }`. `type` is one of `CUSTOMER`, `ACCOUNT`,
393
+ `COUNTERPARTY`, `EMPLOYEE`, `BRANCH`, `PAYMENT_INSTRUMENT` (`EventResourceType`
394
+ — there is no `INSTRUMENT` spelling). The resource must already exist in the
395
+ organization.
396
+
397
+ A bare top-level `customerId` is the deprecated legacy spelling of
398
+ `resource: { type: 'CUSTOMER', id }` and is still accepted; `resource` wins when
399
+ both are sent, and alongside a non-customer `resource` the `customerId` is
400
+ context read by customer-scoped event aggregates. An event naming neither is
401
+ rejected with 400. `deviceId` / `sessionId` qualify an event but never
402
+ substitute for its primary resource.
403
+
404
+ `IngestEventResponse` carries `eventId`, `id`, `type`, the resolved `resource`,
405
+ and `riskSignalIds` — the Risk Signals emitted by the event definition's
406
+ `riskSignalEffects`. Event and signals are written in one transaction, and the
407
+ event's idempotency covers them: a replayed `eventId` echoes the original ids
408
+ (`cached: true`) instead of emitting a second set.
409
+
410
+ ### `riskSignals`
411
+
412
+ | Method | Endpoint |
413
+ | --- | --- |
414
+ | `create(input)` | `POST /v1/risk-signals` |
415
+ | `list(query)` | `GET /v1/risk-signals` |
416
+
417
+ A Risk Signal is time-decaying evidence attached to one resource — evidence,
418
+ **not** an Alert. Creating one raises no alert, notification, assignment or
419
+ task, and does not change the resource's Risk Rating.
420
+
421
+ `create(input)` (`CreateRiskSignalInput`) requires `type`, `resourceType`
422
+ (`RiskSignalResourceType`, the same six values), `resourceId`, `weight` (> 0)
423
+ and `halfLife` (a positive ISO 8601 duration; calendar units Y/M are rejected).
424
+ Optional: `externalId` (idempotency key — a resubmission returns the original
425
+ signal), `occurredAt` (defaults to now), `note`. The server always sets
426
+ `sourceType: 'MANUAL'` and captures the authenticated actor; a caller cannot
427
+ claim a signal came from an Event or a Rule.
428
+
429
+ `list(query)` (`ListRiskSignalsQuery`) requires `resourceType` + `resourceId`
430
+ and accepts `asOf`, `type[]`, `sourceType[]`, `limit` (1–200, default 50) and
431
+ `offset`. It returns `RiskSignalListResponse`: `edges` / `pageInfo` /
432
+ `totalCount` plus `asOf`, `aggregateEffectiveWeight`, `contributingCount`,
433
+ `latestAt` and `uniqueTypeCount`.
434
+
435
+ Decay is `weight * 2 ^ (-ageSeconds / halfLifeSeconds)`; `weight`, `halfLife`
436
+ and `halfLifeSeconds` on a stored signal are an immutable snapshot, while
437
+ `effectiveWeight` is what they have decayed to at `asOf`. The aggregate is
438
+ computed on read, never cached on the resource, so a historical `asOf` returns
439
+ what the resource looked like then. `type` / `sourceType` choose which records
440
+ contribute; they never change how far a contributing signal has decayed.
441
+
442
+ ### `employees`
443
+
444
+ | Method | Endpoint |
445
+ | --- | --- |
446
+ | `list(query?)` | `GET /v1/employees` |
447
+ | `get(id)` | `GET /v1/employees/{id}` |
448
+ | `create(input)` | `POST /v1/employees` |
449
+ | `update(id, input)` | `PATCH /v1/employees/{id}` |
450
+
451
+ People acting inside the financial institution's ecosystem, and an attachable
452
+ resource for Events and Risk Signals. Deliberately distinct from a Dossiers
453
+ organization member: an Employee need not have a Dossiers login, and an
454
+ Employee id is never an org-member id — `orgMemberId` is an optional link, not
455
+ an identity.
456
+
457
+ `create(input)` requires `fullName`; optional `externalEmployeeId` (unique per
458
+ organization, rejected with 400 on a duplicate), `email`, `jobTitle`,
459
+ `department`, `branchId` (must be in the same organization), `status`
460
+ (`ACTIVE` | `INACTIVE` | `SUSPENDED` | `TERMINATED`, default `ACTIVE`) and
461
+ `orgMemberId`. `update` additionally accepts `metadata`.
462
+
463
+ `list` returns `EmployeeListResponse` — `data` plus the connection shape
464
+ (`edges`, `pageInfo`, `meta`). The endpoint declares `cursor` / `limit`, but
465
+ the server currently resolves its own paging defaults (first page, 50 records).
466
+
467
+ ### `branches`
468
+
469
+ | Method | Endpoint |
470
+ | --- | --- |
471
+ | `list(query?)` | `GET /v1/branches` |
472
+ | `get(id)` | `GET /v1/branches/{id}` |
473
+ | `create(input)` | `POST /v1/branches` |
474
+ | `update(id, input)` | `PATCH /v1/branches/{id}` |
475
+
476
+ The institution's own physical or organizational units, through which Employees
477
+ act; also an attachable resource for Events and Risk Signals.
478
+
479
+ `create(input)` requires `name`; optional `externalBranchId` (unique per
480
+ organization), `code`, `country`, `city`, `institutionId` and `status`
481
+ (`ACTIVE` | `INACTIVE` | `CLOSED`, default `ACTIVE`). `update` additionally
482
+ accepts `metadata`. `list` returns `BranchListResponse` with the same shape and
483
+ the same paging caveat as `employees`.
484
+
151
485
  ### `exchangeRates`
152
486
 
153
487
  | Method | Endpoint |
@@ -162,6 +496,42 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
162
496
 
163
497
  `config.rules`, `config.constants`, `config.products`, `config.eventDefinitions`, `config.webhooks` — each exposes `list(query?)`, `create(input)`, `getBySyncKey(syncKey)`, `get(id)`, `update(id, input)`, `delete(id)` on `/v1/config/{resource}`.
164
498
 
499
+ `config.eventDefinitions` create/update accept `riskSignalEffects:
500
+ RiskSignalEffect[]` — the Risk Signals emitted on every valid occurrence of the
501
+ event. Each effect is `{ type, weight, halfLife, target? }`, where `target`
502
+ defaults to `EVENT_RESOURCE` (the event's own primary resource). Effects are
503
+ tenant configuration: an integration pushing an event cannot add, change, or
504
+ suppress one. `weight` and `halfLife` are snapshotted onto each emitted signal,
505
+ so editing a definition never rewrites evidence already recorded.
506
+
507
+ `config.rules` actions accept `ADD_RISK_SIGNAL` with `signalType`, `weight`
508
+ (> 0), `halfLife` (positive ISO 8601 duration) and
509
+ `target: { resource: 'customer' | 'account' | 'counterparty' | 'paymentInstrument' }`.
510
+ `target.resource` is a closed selector over the evaluation, never a field
511
+ expression. `employee` and `branch` are **not** valid — no evaluation envelope
512
+ resolves either slot yet, so such an action could never fire, and both are
513
+ rejected at save time with a 400 saying so. Unlike other action types,
514
+ `ADD_RISK_SIGNAL` may appear several times on one rule; what is rejected is the
515
+ same `signalType` on the same `target.resource` twice, which would double-count
516
+ in every aggregate.
517
+
518
+ `config.customAlertTypes` — org-defined alert types for `alerts.create`.
519
+
520
+ | Method | Endpoint |
521
+ | --- | --- |
522
+ | `list({ lifecycle? })` | `GET /v1/config/custom-alert-types` |
523
+ | `create(input)` | `POST /v1/config/custom-alert-types` |
524
+ | `getByName(name)` | `GET /v1/config/custom-alert-types/by-name/{name}` |
525
+ | `get(id)` | `GET /v1/config/custom-alert-types/{id}` |
526
+ | `update(id, input)` | `PATCH /v1/config/custom-alert-types/{id}` |
527
+ | `archive(id)` / `unarchive(id)` | `POST /v1/config/custom-alert-types/{id}/archive` / `/unarchive` |
528
+ | `delete(id)` | `DELETE /v1/config/custom-alert-types/{id}` (tombstone; archive first) |
529
+
530
+ `name` is snake_case and immutable; `fields` is a `TypedFieldDefinition[]`
531
+ schema. Editing `fields` never re-validates existing alerts — new required
532
+ fields apply to alerts raised afterwards. Archive is refused (409) while any
533
+ alert of the type is OPEN.
534
+
165
535
  `config.currencies`
166
536
 
167
537
  | Method | Endpoint |
@@ -183,7 +553,52 @@ new DossiersOSAPIService({ baseUrl, apiKey, fetch? })
183
553
 
184
554
  | Method | Endpoint |
185
555
  | --- | --- |
186
- | `getSyncExport()` | `GET /v1/settings/sync-export` |
556
+ | `listSyncSnapshots()` | `GET /v1/settings/sync-snapshots` |
557
+ | `getSyncDrift()` | `GET /v1/settings/sync-snapshots/drift` |
558
+ | `createSyncSnapshot(input?)` | `POST /v1/settings/sync-snapshots` |
559
+ | `downloadSyncSnapshot(id)` | `GET /v1/settings/sync-snapshots/{id}/download` |
560
+
561
+ For a typed live configuration export, use `config.sync.export()`.
562
+ Its `SyncConfig.behaviorSettings` singleton carries attempt retention and attempt
563
+ coverage (`ALL` or `SELECTIVE`). The section is optional/unmanaged when absent;
564
+ properties are partial.
565
+
566
+ ### `customLists`
567
+
568
+ Custom screening lists — tenant-managed lists checked by every screening run
569
+ alongside external providers. Archived lists are read-only and never screened;
570
+ disabled lists are skipped but stay editable. List tags (catalogue-governed)
571
+ are shown against every match the list produces.
572
+
573
+ | Method | Endpoint |
574
+ | --- | --- |
575
+ | `list(params?)` | `GET /v1/screening/custom-lists` |
576
+ | `create(input)` | `POST /v1/screening/custom-lists` |
577
+ | `get(listId)` | `GET /v1/screening/custom-lists/{listId}` |
578
+ | `update(listId, input)` | `PATCH /v1/screening/custom-lists/{listId}` |
579
+ | `archive(listId)` | `POST /v1/screening/custom-lists/{listId}/archive` |
580
+ | `unarchive(listId)` | `POST /v1/screening/custom-lists/{listId}/unarchive` |
581
+ | `listEntries(listId, params?)` | `GET /v1/screening/custom-lists/{listId}/entries` |
582
+ | `createEntry(listId, input)` | `POST /v1/screening/custom-lists/{listId}/entries` |
583
+ | `bulkCreateEntries(listId, rows)` | `POST /v1/screening/custom-lists/{listId}/entries/bulk` (≤500 rows/request; per-row rejection; `externalRef` upsert) |
584
+ | `updateEntry(listId, entryId, input)` | `PATCH /v1/screening/custom-lists/{listId}/entries/{entryId}` |
585
+ | `deleteEntry(listId, entryId)` | `DELETE /v1/screening/custom-lists/{listId}/entries/{entryId}` |
586
+
587
+ Entry fields: `name` (required), `entryType` (`INDIVIDUAL` \| `ENTITY`, default
588
+ `INDIVIDUAL` — entries only match subjects of the same type), `aliases`,
589
+ `dateOfBirth` (`YYYY-MM-DD`), `country` (ISO 3166-1 **alpha-3**), `idNumber`,
590
+ `notes`, `externalRef`.
591
+
592
+ ### `reports`
593
+
594
+ On-demand PDF reports. Every method resolves to the raw PDF bytes as a `Uint8Array`; write them to disk (`fs.writeFileSync`) or stream them to your own download response. Gated on `report:read`. Each call records a `report.generated` activity on the subject record.
595
+
596
+ | Method | Endpoint |
597
+ | --- | --- |
598
+ | `reports.alert(alertId)` | `GET /v1/reports/alerts/{alertId}/pdf` |
599
+ | `reports.customerProfile(customerId)` | `GET /v1/reports/customers/{customerId}/pdf` |
600
+ | `reports.counterpartyProfile(counterpartyId)` | `GET /v1/reports/counterparties/{counterpartyId}/pdf` |
601
+ | `reports.resourceProfile(resourceType, resourceId)` | `GET /v1/reports/resources/{resourceType}/{resourceId}/pdf` — `ACCOUNT`, `DEVICE`, `INSTITUTION`, `PRODUCT`, `TASK`, … (`ALERT` / `CUSTOMER` / `COUNTERPARTY` → 400, use the dedicated methods) |
187
602
 
188
603
  ## Typed Fields types
189
604
 
@@ -192,12 +607,15 @@ Available since `v0.2.0`. Spec: [`docs/specs/typed-fields/README.md`](../../../d
192
607
  ```ts
193
608
  type TypedFieldType =
194
609
  | 'TEXT' | 'NUMBER' | 'BOOLEAN' | 'DATE'
195
- | 'SET' | 'CURRENCY' | 'MONETARY';
610
+ | 'LIST' | 'SINGLE_SELECT' | 'MULTI_SELECT'
611
+ | 'CURRENCY' | 'MONETARY'
612
+ | 'SET'; // deprecated
196
613
 
197
614
  interface TypedFieldDefinition {
198
615
  key: string;
199
616
  displayName: string;
200
617
  type: TypedFieldType;
618
+ options?: string[] | null; // required for select types
201
619
  required: boolean;
202
620
  defaultValue?: unknown;
203
621
  description?: string | null;
@@ -208,7 +626,8 @@ interface TypedFieldDefinition {
208
626
  // TEXT / DATE / CURRENCY → string
209
627
  // NUMBER → finite JS number
210
628
  // BOOLEAN → boolean
211
- // SET → string[]
629
+ // LIST / MULTI_SELECT / legacy SET → string[]
630
+ // SINGLE_SELECT → string from the definition's options
212
631
  // MONETARY → denominated literal `"<amount>@<TYPE:CODE>"`
213
632
  interface TypedFieldValueInput {
214
633
  key: string;
@@ -227,6 +646,9 @@ type TypedFieldStored =
227
646
  | { key: string; type: 'number'; value: number }
228
647
  | { key: string; type: 'boolean'; value: boolean }
229
648
  | { key: string; type: 'date'; value: string }
649
+ | { key: string; type: 'list'; value: string[] }
650
+ | { key: string; type: 'single_select'; value: string }
651
+ | { key: string; type: 'multi_select'; value: string[] }
230
652
  | { key: string; type: 'set'; value: string[] }
231
653
  | { key: string; type: 'currency'; value: string }
232
654
  | { key: string; type: 'monetary'; amountMinor: number; currency: string; precision: number };
@@ -239,7 +661,10 @@ DTOs that carry these:
239
661
  | DTO | Field | Tier |
240
662
  | --- | --- | --- |
241
663
  | `CreateAccountDto` / `AccountResponseDto` | `fields`, `otherProperties` | A/B + C |
242
- | `CreateProductDto` / `UpdateProductDto` | `typedAccountFieldConfig`, `fields`, `otherProperties` | schema + B + C |
664
+ | `CreateProductDto` / `UpdateProductDto` | `accountFieldConfig` (`typedAccountFieldConfig` deprecated alias), `fields`, `otherProperties` | schema + B + C |
665
+ | `CreateEventDefinitionDto` / `UpdateEventDefinitionDto` | `properties` (`TypedFieldDefinition[]`) | schema |
666
+ | `CreateCustomAlertTypeDto` / `UpdateCustomAlertTypeDto` | `fields` (`TypedFieldDefinition[]`) | schema |
667
+ | `CreateCustomAlertDto` / `AlertResponseDto` | `fields` | A |
243
668
  | `CounterpartyResponseDto` | `otherProperties` | C |
244
669
  | Event ingestion (`POST /v1/events`) | `fields` | B |
245
670
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dossier-wiki/os-sdk",
3
- "version": "0.7.0",
3
+ "version": "1.4.0",
4
4
  "description": "TypeScript SDK for the Dossiers OS API — customers, accounts, transactions, rules, screening, and configuration.",
5
5
  "license": "MIT",
6
6
  "type": "module",