@dossier-wiki/cloud-sdk
v0.8.3
Published
Type-safe TypeScript SDK for the Dossiers API
Readme
@dossier-wiki/cloud-sdk
The official TypeScript SDK for the Dossiers API.
Covers AML screening, KYC session management, AI-powered document processing, form templates, and webhook verification — all with full TypeScript types and optional Zod runtime validation.
Table of Contents
- Installation
- Quick Start
- Migrating from the flat API (v0.1)
- Authentication
- App Info, API Versions & Contract Notices
- Error Handling
- AML Screening
- KYC Sessions
- KYC Workflows
- Document Processing
- Form Templates
- Media
- Webhooks
- TypeScript
Installation
npm install @dossier-wiki/cloud-sdk
# or
pnpm add @dossier-wiki/cloud-sdkRequires Node.js 18+ (or any runtime with a global fetch and crypto.subtle).
Quick Start
import { DossiersAPIService } from '@dossier-wiki/cloud-sdk';
const service = new DossiersAPIService({
apiKey: process.env.DOSSIERS_API_KEY!,
});
// AML — search sanctions
const { totalHits, documents } = await service.aml.searchSanctions({
fullName: 'John Doe',
});
// KYC — create a session
const { sessionUrl } = await service.kyc.sessions.create({
customer: { name: 'Alice Brown', email: '[email protected]' },
workflowId: 'wf_abc123',
});
// Document processing
const instance = await service.documents.initiate({
processorId: 'INVOICE',
files: [{ url: 'https://example.com/invoice.pdf', mimeType: 'application/pdf' }],
});Migrating from the flat API (v0.1)
Version 0.1 exposed every operation as a method directly on the service
(service.searchSanctions(), service.createKycSession(), ...). That top-level
surface grew too large to navigate, so operations are now grouped into resource
namespaces.
The old flat methods still work — they are thin, deprecated wrappers that forward to the new resource methods, so existing code keeps running unchanged. Your editor will show them struck through; migrate at your own pace. They will be removed in a future major version.
| Deprecated flat method | Use instead |
|------------------------|-------------|
| service.searchSanctions(input) | service.aml.searchSanctions(input) |
| service.searchDossiers(input) | service.aml.searchDossiers(input) |
| service.createScreeningRequest(input) | service.aml.createScreeningRequest(input) |
| service.createKycSession(input) | service.kyc.sessions.create(input) |
| service.listKycSessions(filters?) | service.kyc.sessions.list(filters?) |
| service.getKycSession(id) | service.kyc.sessions.get(id) |
| service.deleteKycSession(id) | service.kyc.sessions.delete(id) |
| service.initiateDocumentProcessing(input) | service.documents.initiate(input) |
| service.listDocumentProcessingInstances(query?) | service.documents.list(query?) |
| service.listDocumentProcessors() | service.documents.listProcessors() |
| service.getDocumentProcessingInstance(id) | service.documents.get(id) |
| service.getDocumentProcessingStage(id, stageId) | service.documents.getStage(id, stageId) |
| service.deleteDocumentProcessingInstance(id) | service.documents.delete(id) |
| service.streamDocumentProcessingEvents(id, init?) | service.documents.stream(id, init?) |
New capabilities added since v0.1 — KYC workflows (service.kyc.workflows),
form templates (service.formTemplates), media access (service.media), and
KYC session webhook events — are only available on the resource API.
Authentication
All requests are authenticated with your API key as a Bearer token. Set it once on the service instance — never include it in client-side code.
const service = new DossiersAPIService({
apiKey: process.env.DOSSIERS_API_KEY!,
// Optional: override the base URL (e.g. for local development)
baseUrl: 'http://localhost:4000',
// Optional: inject a custom fetch (useful for testing or proxying)
fetch: myCustomFetch,
});App Info, API Versions & Contract Notices
Every request carries the Cloud API version this SDK build speaks — a dated
contract, exported as DOSSIERS_API_VERSION (2026-10-01) — in the
Dossiers-Api-Version header. The API only ever changes behaviour on that
version; within a version, changes are additive, so ignore response fields you
don't know.
Tell Dossiers what is calling with appInfo. It is recorded against the API key
(shown in the Developer Console) and never changes how the API behaves:
const service = new DossiersAPIService({
apiKey: process.env.DOSSIERS_API_KEY!,
appInfo: {
name: 'dossiers-os',
version: '1.5.0',
commit: process.env.GIT_SHA, // optional, first 8 chars are sent
installationId: process.env.INSTALL_ID, // optional, sent as Dossiers-Installation-Id
},
// Called when the API says this SDK's API version is deprecated, has a
// sunset date, or is no longer accepted (426).
onContractNotice: (notice) => {
// { apiVersion, status, deprecation?, sunset?, link?, message?, minimumApiVersion? }
logger.warn('Dossiers API contract notice', notice);
},
});Requests then carry:
User-Agent: dossiers-os/1.5.0 (17bbc509) cloud-sdk/0.8.0 node/22.19.0
Dossiers-Api-Version: 2026-10-01
Dossiers-Installation-Id: 6f1c…User-Agent is never set in browsers (they forbid it); the other headers always
are.
Calling the API with fetch directly? Use the same helpers:
import { buildDossiersRequestHeaders, readContractNotice } from '@dossier-wiki/cloud-sdk';
const res = await fetch(url, {
headers: { ...buildDossiersRequestHeaders(appInfo), Authorization: `Bearer ${key}` },
});
const notice = readContractNotice(res); // null unless Deprecation/Sunset or 426CLOUD_SDK_VERSION exports the SDK's own version.
Error Handling
All SDK methods throw a DossiersAPIError on non-2xx responses.
import { DossiersAPIService, DossiersAPIError } from '@dossier-wiki/cloud-sdk';
try {
const session = await service.kyc.sessions.get('ses_nonexistent');
} catch (err) {
if (err instanceof DossiersAPIError) {
console.error(err.status); // e.g. 404
console.error(err.message); // human-readable message from the API
console.error(err.body); // raw parsed response body
}
}A 426 Upgrade Required — the API no longer accepts this SDK's API version —
throws DossiersUpgradeRequiredError (a DossiersAPIError subclass) with
minimumApiVersion. Upgrade the SDK to a release that speaks it.
AML Screening
Sanctions Search
Search global sanctions lists (OFAC, UN, FCDO, DFAT, CASL, INTERPOL) for individuals or entities.
const results = await service.aml.searchSanctions({
fullName: 'Jane Smith',
// Optional: narrow to specific lists
sources: ['ofac', 'un'],
// Optional: filter by country
countries: ['US', 'GB'],
// Optional: match against document numbers
idNumbers: ['P123456789'],
});
console.log(results.totalHits);
for (const sanction of results.documents) {
console.log(sanction.name, sanction.source, sanction.sanctionsProgramme);
}PEP & Adverse Media (Dossiers)
Search PEP registries and adverse media databases. Returns individual dossiers with risk scores, positions held, and associates.
const dossiers = await service.aml.searchDossiers({
fullName: 'Robert Jones',
onlyShowHighRiskProfiles: true,
noCache: false, // use cached results (default)
});
for (const dossier of dossiers) {
const score = dossier.riskScorecard?.overallRiskScore ?? 0;
console.log(dossier.fullName, score, dossier.riskScorecard?.riskCategory);
}Ongoing Screening Requests
Submit individuals for continuous monitoring. Webhook events fire when screening completes or when new alerts are triggered.
const { requestId } = await service.aml.createScreeningRequest({
fullName: 'Alice Brown',
externalId: 'cust_123', // your internal reference ID
dateOfBirth: '1985-06-15',
country: 'GB',
identificationDocument: {
type: 'passport',
number: 'AB123456',
country: 'GB',
},
});KYC Sessions
A KYC session is a single customer verification journey. The customer receives
a link (sessionUrl) they visit to complete the configured workflow steps.
// Create a session
const { session, sessionUrl, token } = await service.kyc.sessions.create({
customer: {
name: 'Nimal Perera',
email: '[email protected]',
externalId: 'cust_001', // your internal reference
},
workflowId: 'wf_standard_kyc',
expiresInMs: 86_400_000, // 24 hours (default: 1 hour)
});
// Send the sessionUrl to your customer
await sendInvitationEmail(session.customer.email, sessionUrl);
// List sessions
const { sessions, totalCount } = await service.kyc.sessions.list({
isComplete: true,
sortBy: 'completedAt',
sortOrder: 'desc',
limit: 50,
offset: 0,
});
// Get a specific session (includes full step history & media)
const fullSession = await service.kyc.sessions.get(session.id);
console.log(fullSession.status); // 'complete' | 'in-progress' | 'approved' | ...
// Delete a session
await service.kyc.sessions.delete(session.id);KYC Workflows
A workflow defines the ordered steps a customer goes through during a session (e.g. identity verification → liveness → form collection).
Building a Workflow
Use stepDefaults() to get pre-built configurations for each step type, then
assemble them into a workflow.
import type { WorkflowRules } from '@dossier-wiki/cloud-sdk';
// Fetch defaults for all step types
const defaults = await service.kyc.workflows.stepDefaults();
const idStep = defaults.find(d => d.type === 'id_verification')!;
const livenessStep = defaults.find(d => d.type === 'liveness')!;
// Create the workflow
const workflow = await service.kyc.workflows.create({
name: 'Standard KYC',
description: 'Identity check + liveness verification',
steps: [
{
stepId: idStep.defaultStepId,
type: 'id_verification',
input: idStep.defaultInput as { config?: Record<string, unknown> },
output: { schema: idStep.schema },
rules: idStep.defaultRules as WorkflowRules,
locales: idStep.defaultLocales,
skippable: idStep.skippable,
},
{
stepId: livenessStep.defaultStepId,
type: 'liveness',
input: livenessStep.defaultInput as { config?: Record<string, unknown> },
output: { schema: livenessStep.schema },
rules: livenessStep.defaultRules as WorkflowRules,
locales: livenessStep.defaultLocales,
skippable: livenessStep.skippable,
},
],
});
// Use the workflow when creating sessions
const { sessionUrl } = await service.kyc.sessions.create({
customer: { name: 'Bob Lee' },
workflowId: workflow.id,
});Zod Validation
The SDK exports Zod schemas for the step discriminated union, so you can validate workflow input at runtime before sending it to the API.
import { schemas } from '@dossier-wiki/cloud-sdk';
// Validates the full workflow creation input including per-step type constraints
const validated = schemas.CreateWorkflowSchema.parse(rawInput);
const workflow = await service.kyc.workflows.create(validated);
// Validate a single step
const step = schemas.WorkflowStepSchema.parse({
stepId: 'step_liveness',
type: 'liveness',
input: { config: { challengeType: 'selfie', timeLimitSeconds: 30 } },
output: {},
rules: { onSuccess: 'COMPLETE_SESSION', onFailure: 'FAIL_SESSION' },
});Available step types and their config constraints:
| Type | Config fields |
|------|--------------|
| id_verification | strategy, allowedDocuments, maxSubmissions, allowUpload |
| document_collection | allowedDocuments, allowUpload, maxSubmissions |
| liveness | challengeType, timeLimitSeconds, minFaceLuminance, minFaceDetectionConfidence |
| form_collection | templateId, fields |
| esignature | signatureTypesAllowed |
| action | actionType |
Other workflow operations:
// List all workflows
const workflows = await service.kyc.workflows.list();
// Get a specific workflow
const wf = await service.kyc.workflows.get('wf_abc123');
// Update (creates a new version; existing sessions are unaffected)
const updated = await service.kyc.workflows.update('wf_abc123', {
name: 'Standard KYC v2',
});
// Delete
await service.kyc.workflows.delete('wf_old123');Document Processing
Initiate AI extraction jobs on uploaded documents. The pipeline validates the document, extracts structured data, and runs post-processing checks.
Available processor IDs:
| processorId | Document |
|---------------|---------|
| INVOICE | Invoice / bill |
| SRI_LANKA_ID | Sri Lanka National Identity Card |
| SRI_LANKA_FORM1_COMPANY_REGISTRATION | Company Registration Form 1 |
| SRI_LANKA_FORM15_ANNUAL_RETURN | Annual Return Form 15 |
Initiate a Job
const instance = await service.documents.initiate({
processorId: 'INVOICE',
files: [
{ url: 'https://example.com/invoice.pdf', mimeType: 'application/pdf' },
],
// Optional: arbitrary metadata stored on the instance
metadata: { orderId: 'ord_456' },
// Default: true — runs asynchronously; false waits for completion
backgroundProcessing: true,
});
console.log(instance.instanceId); // use this to poll or streamPoll for Results
let instance = await service.documents.get(instanceId);
while (instance.status === 'pending' || instance.status === 'running') {
await new Promise(r => setTimeout(r, 2000));
instance = await service.documents.get(instanceId);
}
if (instance.status === 'completed') {
console.log(instance.result?.validation.passed);
console.log(instance.result?.normalizedData);
}Real-Time Progress (SSE)
const res = await service.documents.stream(instanceId);
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true });
// Each SSE line is a DocumentProcessingEvent JSON payload
console.log(text);
}Typed Extraction Schemas
Use the generic parameter to type the result.schema field for a specific processor:
import type {
DocumentProcessingExtractedField,
DocumentProcessingTransliteratedField,
} from '@dossier-wiki/cloud-sdk';
type SriLankaIdSchema = {
name: DocumentProcessingTransliteratedField;
idNumber: DocumentProcessingExtractedField<string>;
dateOfBirth: DocumentProcessingExtractedField<string>;
address: DocumentProcessingTransliteratedField;
gender: DocumentProcessingExtractedField<string>;
};
const instance = await service.documents.initiate<SriLankaIdSchema>({
processorId: 'SRI_LANKA_ID',
files: [{ base64Data: '...', mimeType: 'image/jpeg' }],
});
// result.schema is now typed as SriLankaIdSchema
const name = instance.result?.schema.name.enTransliterations[0].transliteratedText;Other document processing operations:
// List instances
const { instances, count } = await service.documents.list({
processorId: 'INVOICE',
status: 'completed',
limit: 20,
});
// Get a specific stage result
const stage = await service.documents.getStage(instanceId, 'post_validation');
console.log(stage.data.validation.passed);
// List available processors
const processors = await service.documents.listProcessors();
// Delete an instance
await service.documents.delete(instanceId);Form Templates
Form templates define reusable sets of fields for the form_collection KYC
workflow step.
// Create a template
const template = await service.formTemplates.create({
name: 'Employment Details',
fields: [
{
id: 'employer',
type: 'text',
label: 'Employer Name',
required: true,
},
{
id: 'salary',
type: 'currency',
label: 'Annual Salary (LKR)',
required: true,
validation: { min: 0 },
},
{
id: 'employment_type',
type: 'select',
label: 'Employment Type',
required: true,
options: [
{ value: 'full_time', label: 'Full Time' },
{ value: 'part_time', label: 'Part Time' },
{ value: 'self_employed', label: 'Self Employed' },
],
},
],
});
// Reference the template in a form_collection step config
const workflowStep = {
type: 'form_collection' as const,
input: { config: { templateId: template.id } },
// ...
};
// List, get, update, delete
const { templates } = await service.formTemplates.list({ searchQuery: 'employment' });
const full = await service.formTemplates.get(template.id);
await service.formTemplates.update(template.id, { name: 'Employment Details v2' });
await service.formTemplates.delete(template.id);Available field types: text, long_text, number, currency, country,
language, email, phone, date, select, multi_select, dropdown,
toggle, boolean, file, heading, paragraph, description, divider.
Media
Fetch binary files (images, videos, PDF signatures) from session results and document processing instances using signed storage tokens.
// Tokens appear in session result media arrays and processing instance files
const token = session.result.steps[0].summary?.media[0]?.key;
if (token) {
const res = await service.media.get(token);
// Browser — convert to object URL for display
const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);
imageElement.src = objectUrl;
// Node.js — pipe to disk
const { createWriteStream } = await import('fs');
const { Readable } = await import('stream');
Readable.fromWeb(res.body!).pipe(createWriteStream('./capture.jpg'));
}Webhooks
Use DossiersWebhookRouter to handle incoming webhook events with HMAC-SHA256
signature verification.
import { DossiersWebhookRouter, DossiersWebhookEventTypes } from '@dossier-wiki/cloud-sdk';
const router = new DossiersWebhookRouter(process.env.DOSSIERS_WEBHOOK_SECRET!);
router
.on(DossiersWebhookEventTypes.DOCUMENT_VERIFICATION_INSTANCE_COMPLETED, async (event, ctx) => {
const { documentProcessorInstanceId } = event.data;
const instance = await service.documents.get(documentProcessorInstanceId);
await saveResult(instance);
})
.on(DossiersWebhookEventTypes.KYC_SESSION_COMPLETED, async (event, ctx) => {
// Payloads carry no PII — fetch the full session on demand.
const { kycSessionId, outcome } = event.data;
const session = await service.kyc.sessions.get(kycSessionId);
await recordVerification(session, outcome); // outcome: 'complete' | 'failed'
})
.on(DossiersWebhookEventTypes.AML_SCREENING_ALERT_TRIGGERED, async (event, ctx) => {
console.log('Alert triggered for screening request', event.data);
});
// Express
app.post('/webhooks/dossiers', async (req, res) => {
// Reconstruct a Web API Request from the Express request
const body = await getRawBody(req);
const webReq = new Request('https://your-server/webhooks/dossiers', {
method: 'POST',
headers: req.headers as HeadersInit,
body,
});
const response = await router.handle(webReq);
res.status(response.status).send(await response.text());
});
// Next.js App Router
export async function POST(req: Request) {
return router.handle(req, { waitUntil: (p) => context.waitUntil(p) });
}Webhook events are acknowledged immediately (200 OK) before the handler runs — a slow handler will not cause the platform to retry. If your handler throws, the error is logged but no retry occurs. Implement your own retry logic inside the handler if needed.
Event types this SDK build doesn't know — Dossiers adds them without a new API version — are acknowledged with 200 and ignored, as are events you registered no handler for.
Every event delivered to a handler has the shape:
{
id: string; // e.g. "evt_1712345678_ab12cd34"
type: DossiersWebhookEventTypes;
organizationId: string;
apiVersion: string; // the destination's Cloud API version, e.g. "2026-10-01"
eventTriggeredAt: string; // ISO 8601
data: /* typed per event — see below */;
}Registering a Destination
service.webhooks.destinations.upsert() creates your destination for a URL, or
updates the existing one — safe to call on every start. It needs an API key
with the Webhooks permission (WEBHOOKS_MANAGE); without it the call throws
a DossiersAPIError with status 403.
const { id, secret, created } = await service.webhooks.destinations.upsert({
url: 'https://your-app.example.com/webhooks/dossiers',
events: [
DossiersWebhookEventTypes.KYC_SESSION_COMPLETED,
DossiersWebhookEventTypes.KYC_SESSION_REVIEWED,
],
name: 'My app', // optional; defaults to the URL's host
// apiVersion defaults to DOSSIERS_API_VERSION, and installationId to the
// Dossiers-Installation-Id header the SDK sends from appInfo.
});
const router = new DossiersWebhookRouter(secret);The signing secret is generated on create and kept (and returned again) on
update. Fields you leave out are left as they are, and a destination disabled
in the Developer Console stays disabled (isActive: false).
The data field is fully typed by the event type you pass to .on(). Import
DossiersWebhookPayload for the discriminated union of all event shapes.
Available Event Types
Document processing
| Enum value | Event string | data type |
|------------|-------------|-------------|
| DOCUMENT_VERIFICATION_INSTANCE_CREATED | documentVerification.instance.created | DocumentVerificationInstanceCreatedData |
| DOCUMENT_VERIFICATION_INSTANCE_UPDATED | documentVerification.instance.updated | DocumentVerificationInstanceUpdatedData |
| DOCUMENT_VERIFICATION_INSTANCE_COMPLETED | documentVerification.instance.completed | DocumentVerificationInstanceCompletedData |
| DOCUMENT_VERIFICATION_INSTANCE_DELETED | documentVerification.instance.deleted | DocumentVerificationInstanceDeletedData |
KYC sessions
| Enum value | Event string | data type |
|------------|-------------|-------------|
| KYC_SESSION_CREATED | kycSessions.session.created | KycSessionEventData |
| KYC_SESSION_UPDATED | kycSessions.session.updated | KycSessionEventData |
| KYC_SESSION_COMPLETED | kycSessions.session.completed | KycSessionEventData (with outcome) |
| KYC_SESSION_REVIEWED | kycSessions.session.reviewed | KycSessionEventData (with review) |
| KYC_SESSION_DELETED | kycSessions.session.deleted | KycSessionDeletedData |
| KYC_STEP_COMPLETED | kycSessions.step.completed | KycStepEventData |
| KYC_STEP_FAILED | kycSessions.step.failed | KycStepEventData |
| KYC_SESSION_COMMENT_ADDED | kycSessions.session.commentAdded | KycSessionCommentAddedData |
| KYC_SESSION_COMMENT_UPDATED | kycSessions.session.commentUpdated | KycSessionCommentUpdatedData |
| KYC_SESSION_COMMENT_DELETED | kycSessions.session.commentDeleted | KycSessionCommentDeletedData |
KYC payloads carry no PII (no customer name/email, no step outputs). They reference a
kycSessionId— fetch the detail you need withservice.kyc.sessions.get(kycSessionId).
AML screening
| Enum value | Event string |
|------------|-------------|
| AML_SCREENING_REQUEST_CREATED | amlScreening.screeningRequest.created |
| AML_SCREENING_REQUEST_COMPLETED | amlScreening.screeningRequest.completed |
| AML_SCREENING_ALERT_TRIGGERED | amlScreening.screeningAlert.triggered |
| AML_SCREENING_ALERT_STATUS_CHANGED | amlScreening.screeningAlert.statusChanged |
| AML_SCREENING_ALERT_COMMENT_ADDED | amlScreening.screeningAlert.commentAdded |
| AML_SCREENING_ALERT_COMMENT_UPDATED | amlScreening.screeningAlert.commentUpdated |
| AML_SCREENING_ALERT_COMMENT_DELETED | amlScreening.screeningAlert.commentDeleted |
| AML_SCREENING_ALERT_DELETED | amlScreening.screeningAlert.deleted |
System
| Enum value | Event string |
|------------|-------------|
| SYSTEM_TEST_EVENT | system.testEvent |
| SYSTEM_PRODUCT_ACCESS_GRANTED | system.product.accessGranted |
| SYSTEM_PRODUCT_ACCESS_REVOKED | system.product.accessRevoked |
| SYSTEM_API_KEY_CREATED | system.apiKey.created |
| SYSTEM_API_KEY_INVALIDATED | system.apiKey.invalidated |
| SYSTEM_USAGE_LIMIT_REACHED | system.usage.limitReached |
| SYSTEM_USAGE_LIMIT_WARNING | system.usage.limitWarning |
TypeScript
All API request and response shapes are exported as TypeScript interfaces. Import them as needed:
import type {
// AML
SanctionSearchInput,
SanctionSearchResponse,
Sanction,
DossierSearchInput,
IndividualDossier,
CreateScreeningRequestDto,
// KYC Sessions
CreateKycSessionDTO,
KycSessionOutputDTO,
KycSessionFiltersDTO,
PaginatedKycSessionsDTO,
// KYC Workflows
CreateKycWorkflowInput,
KycWorkflow,
KycWorkflowStep,
KycWorkflowStepType,
WorkflowRules,
WorkflowStepDefault,
// Document Processing
InitiateDocumentProcessingInput,
DocumentProcessingInstance,
DocumentProcessingResult,
DocumentProcessingExtractedField,
DocumentProcessingTransliteratedField,
DocumentProcessorId,
// Form Templates
CreateFormTemplateInput,
FormTemplate,
FormField,
FormFieldType,
// Webhooks
DossiersWebhookPayload,
DossiersWebhookEvent,
UpsertWebhookDestinationInput,
UpsertWebhookDestinationResult,
// Client identity & API version contract
DossiersAppInfo,
DossiersContractNotice,
KycSessionEventData,
KycSessionDeletedData,
KycStepEventData,
KycSessionCommentAddedData,
KycSessionCommentUpdatedData,
KycSessionCommentDeletedData,
} from '@dossier-wiki/cloud-sdk';