@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/README.md +401 -14
- package/dist/alert-workflow-types.d.ts +115 -0
- package/dist/alert-workflow-types.d.ts.map +1 -0
- package/dist/alert-workflow-types.js +2 -0
- package/dist/alert-workflow-types.js.map +1 -0
- package/dist/service.d.ts +593 -15
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +871 -18
- package/dist/service.js.map +1 -1
- package/dist/types.d.ts +2454 -100
- package/dist/types.d.ts.map +1 -1
- package/docs/REFERENCE.md +430 -5
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
| '
|
|
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
|
|
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