@docmana/sdk 0.18.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,11 +1,28 @@
1
- # Changelog
2
-
3
- ## 0.18.0
4
- - Expose the optional aggregate `verdict` on `DocmanaExecutionResult`, preserving
5
- the `VALID`, `PARTIAL`, `WARNING`, `FAILED`, or `UNKNOWN` value computed by
6
- Docmana API and returned by consumer-edge.
7
-
8
- ## 0.17.0
1
+ # Changelog
2
+
3
+ ## 0.20.0 - 2026-08-04
4
+
5
+ - Add `detectFraud` to start a Consumer Edge fraud-detection job, poll its
6
+ status, and return the normalized verdict and findings.
7
+ - Add `detectFraudAsync`, `getFraudDetectionStatus`, and
8
+ `getFraudDetectionResult` for callers that manage polling themselves.
9
+ - Respect `X-Poll-After` and `Retry-After`, retry transient status failures,
10
+ and expose typed failure, timeout, and cancellation errors.
11
+
12
+ ## 0.19.0 - 2026-08-04
13
+
14
+ - Add synchronous and asynchronous V2 partial flow execution through Consumer Edge.
15
+ - Use `executionScope`, `flowVersion`, and `classificationNodeNames` in the
16
+ multipart body, leaving only `organization` in the query string.
17
+ - Return the standard `DocmanaExecutionResult` shape with optional
18
+ `partialExecution` metadata.
19
+
20
+ ## 0.18.0
21
+ - Expose the optional aggregate `verdict` on `DocmanaExecutionResult`, preserving
22
+ the `VALID`, `PARTIAL`, `WARNING`, `FAILED`, or `UNKNOWN` value computed by
23
+ Docmana API and returned by consumer-edge.
24
+
25
+ ## 0.17.0
9
26
  - Expose the optional `flowVersion` on `DocmanaExecutionResult`, preserving the
10
27
  exact Docmana flow version used by an execution in polling and callback results.
11
28
 
package/README.md CHANGED
@@ -32,6 +32,21 @@ const result = await docmana.runFlow("<flow-id>", {
32
32
  console.log(result.status, result.executionId);
33
33
  ```
34
34
 
35
+ Fraud detection follows the same asynchronous Consumer Edge pattern. The
36
+ high-level call starts the job, polls Consumer, and returns the normalized
37
+ result:
38
+
39
+ ```ts
40
+ const fraud = await docmana.detectFraud({
41
+ documentUrl: "https://downloads.example.com/claim.pdf?token=...",
42
+ filename: "claim.pdf",
43
+ analystContext: "Travel claim; verify the invoice total.",
44
+ organization: "acme",
45
+ });
46
+
47
+ console.log(fraud.verdict, fraud.confidence, fraud.findings);
48
+ ```
49
+
35
50
  ## Authentication
36
51
 
37
52
  The public SDK is Bearer-only. It does not acquire OAuth tokens and it never
@@ -53,16 +68,16 @@ static `accessToken`, `401` is returned as an auth error without retry.
53
68
 
54
69
  ## Configuration
55
70
 
56
- | Field | Type | Required | Description |
57
- | ------------------- | --------------------------------------------------------------------- | -------- | ------------------------------------------------------------ |
58
- | `apiBaseUrl` | `string` | Yes | Base URL of Consumer API, including version when applicable. |
59
- | `accessToken` | `string` | One of | Static Bearer token sent to Consumer. |
60
- | `getAccessToken` | `(options?: { forceRefresh?: boolean }) => string \| Promise<string>` | One of | Token provider for renewable Bearer tokens. |
61
- | `timeoutMs` | `number` | No | Maximum polling time for `runFlow`. Default: 5 minutes. |
62
- | `pollIntervalMs` | `number` | No | Fallback minimum delay between status polls. Default: 2s. |
63
- | `pollMaxIntervalMs` | `number` | No | Fallback maximum polling delay. Default: 10s. |
64
- | `fetch` | `typeof fetch` | No | Custom fetch implementation. |
65
- | `headers` | `Record<string, string>` | No | Extra headers sent on every Consumer request. |
71
+ | Field | Type | Required | Description |
72
+ | ------------------- | --------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------- |
73
+ | `apiBaseUrl` | `string` | Yes | Base URL of Consumer API, including version when applicable. |
74
+ | `accessToken` | `string` | One of | Static Bearer token sent to Consumer. |
75
+ | `getAccessToken` | `(options?: { forceRefresh?: boolean }) => string \| Promise<string>` | One of | Token provider for renewable Bearer tokens. |
76
+ | `timeoutMs` | `number` | No | Maximum polling time for `runFlow` and `detectFraud`. Default: 5 minutes. |
77
+ | `pollIntervalMs` | `number` | No | Fallback minimum delay between status polls. Default: 2s. |
78
+ | `pollMaxIntervalMs` | `number` | No | Fallback maximum polling delay. Default: 10s. |
79
+ | `fetch` | `typeof fetch` | No | Custom fetch implementation. |
80
+ | `headers` | `Record<string, string>` | No | Extra headers sent on every Consumer request. |
66
81
 
67
82
  `apiBaseUrl` is required so a migrated SDK cannot accidentally call
68
83
  `https://api.docmana.ai`.
@@ -115,6 +130,11 @@ use `RunFlowParams` for request data:
115
130
  - `runFlowAsync(flowId, params, options?)` posts multipart files to
116
131
  `POST /v1/flows/:flowId/execute-async` and returns
117
132
  `{ executionResultId, fileIds }`.
133
+ - `runPartialFlow(flowId, params, options?)` executes classification, extraction,
134
+ or extraction with validation synchronously through Consumer Edge. It returns
135
+ the standard `DocmanaExecutionResult` with `partialExecution` metadata.
136
+ - `runPartialFlowAsync(flowId, params, options?)` starts the same partial execution
137
+ asynchronously and returns `{ executionResultId, fileIds }` for standard polling.
118
138
  - `getStatus(executionResultId)` calls
119
139
  `GET /v1/executions/:executionResultId/status`.
120
140
  - `getResult(executionResultId, signal?, language?)` calls
@@ -123,12 +143,33 @@ use `RunFlowParams` for request data:
123
143
  - `runFlow(flowId, params, options?)` starts async execution, polls status, fetches the
124
144
  result, respects `X-Poll-After` and `429 Retry-After` while polling, and throws
125
145
  `DocmanaExecutionError` if the result status is `FAILED`.
146
+ - `detectFraud(params, options?)` starts fraud detection from `documentUrl`, polls
147
+ Consumer until `COMPLETED` or `FAILED`, and returns the normalized verdict and findings.
148
+ - `detectFraudAsync(params, options?)` starts fraud detection and immediately returns
149
+ `{ jobId, status }` without polling.
150
+ - `getFraudDetectionStatus(jobId, signal?, organization?)` and
151
+ `getFraudDetectionResult(jobId, signal?, organization?)` provide the lower-level
152
+ polling workflow.
126
153
 
127
154
  Consumer is the polling authority. When `GET /status` returns `X-Poll-After`,
128
155
  the SDK waits that many milliseconds before the next status call. If the header
129
156
  is absent, the SDK falls back to capped exponential full jitter between
130
157
  `pollIntervalMs` and `pollMaxIntervalMs`. `Retry-After` on `429` always wins.
131
158
 
159
+ The SDK does not log or return `documentUrl`. Signed query parameters are sent
160
+ only in the JSON request body to Consumer Edge.
161
+
162
+ ```ts
163
+ const partial = await docmana.runPartialFlow("<flow-id>", {
164
+ files: ["./contract.pdf"],
165
+ executionScope: "extraction_and_validation",
166
+ flowVersion: "published",
167
+ classificationNodeNames: ["classificationContract"],
168
+ });
169
+
170
+ console.log(partial.documents, partial.partialExecution);
171
+ ```
172
+
132
173
  ```ts
133
174
  const flows = await docmana.listFlows();
134
175
  console.log(flows.map((flow) => `${flow.id}: ${flow.name} (${flow.state})`).join("\n"));
@@ -261,15 +302,16 @@ from the Bearer token.
261
302
  Every SDK error extends `DocmanaError`, which carries `.status`, `.code`, and an
262
303
  optional `.requestId` when available.
263
304
 
264
- | Error | Thrown when |
265
- | ------------------------ | -------------------------------------------------------------------------- |
266
- | `DocmanaRequestError` | `400` bad request. |
267
- | `DocmanaAuthError` | `401` rejected or expired Bearer token. |
268
- | `DocmanaPermissionError` | `403` missing permission. |
269
- | `DocmanaNotFoundError` | `404` flow or execution not found. |
270
- | `DocmanaExecutionError` | The flow finished with status `"FAILED"`. Carries `.errors` and `.result`. |
271
- | `DocmanaTimeoutError` | Polling exceeded `timeoutMs`. |
272
- | `DocmanaAbortError` | The provided `AbortSignal` fired. |
305
+ | Error | Thrown when |
306
+ | ---------------------------- | ------------------------------------------------------------------------------------------- |
307
+ | `DocmanaRequestError` | `400` bad request. |
308
+ | `DocmanaAuthError` | `401` rejected or expired Bearer token. |
309
+ | `DocmanaPermissionError` | `403` missing permission. |
310
+ | `DocmanaNotFoundError` | `404` flow or execution not found. |
311
+ | `DocmanaExecutionError` | The flow finished with status `"FAILED"`. Carries `.errors` and `.result`. |
312
+ | `DocmanaFraudDetectionError` | Fraud detection finished with status `"FAILED"`. Carries `.jobId`, `.error`, and `.result`. |
313
+ | `DocmanaTimeoutError` | Polling exceeded `timeoutMs`. |
314
+ | `DocmanaAbortError` | The provided `AbortSignal` fired. |
273
315
 
274
316
  ## Local development with yalc
275
317
 
@@ -0,0 +1,5 @@
1
+ import type { HttpClient } from "../http/http-client.js";
2
+ import type { FraudDetectionAccepted, FraudDetectionParams, FraudDetectionResult, FraudDetectionStatusResult } from "../types.js";
3
+ export declare function startFraudDetection(http: HttpClient, params: FraudDetectionParams, signal?: AbortSignal): Promise<FraudDetectionAccepted>;
4
+ export declare function getFraudDetectionStatus(http: HttpClient, jobId: string, signal?: AbortSignal, organization?: string): Promise<FraudDetectionStatusResult>;
5
+ export declare function getFraudDetectionResult(http: HttpClient, jobId: string, signal?: AbortSignal, organization?: string): Promise<FraudDetectionResult>;
@@ -0,0 +1,8 @@
1
+ import type { ResolvedPart } from "../files/resolve-input.js";
2
+ import type { HttpClient } from "../http/http-client.js";
3
+ import type { DocmanaExecutionResult, RunPartialFlowAsyncParams, RunPartialFlowParams } from "../types.js";
4
+ export declare function runPartialFlow(http: HttpClient, flowId: string, parts: ResolvedPart[], params: RunPartialFlowParams, signal?: AbortSignal): Promise<DocmanaExecutionResult>;
5
+ export declare function runPartialFlowAsync(http: HttpClient, flowId: string, parts: ResolvedPart[], params: RunPartialFlowAsyncParams, signal?: AbortSignal): Promise<{
6
+ executionResultId: string;
7
+ fileIds: string[];
8
+ }>;
package/dist/cli.mjs CHANGED
@@ -79,6 +79,17 @@ var DocmanaExecutionError = class extends DocmanaError {
79
79
  this.result = result;
80
80
  }
81
81
  };
82
+ var DocmanaFraudDetectionError = class extends DocmanaError {
83
+ jobId;
84
+ error;
85
+ result;
86
+ constructor(message, result) {
87
+ super(message, { code: "fraud_detection_failed" });
88
+ this.jobId = result.jobId;
89
+ this.error = result.error;
90
+ this.result = result;
91
+ }
92
+ };
82
93
  var DocmanaTimeoutError = class extends DocmanaError {
83
94
  constructor(message) {
84
95
  super(message, { code: "timeout" });
@@ -282,6 +293,39 @@ async function runFlow(http, flowId, parts, signal, useDraftVersion = false, con
282
293
  );
283
294
  }
284
295
 
296
+ // src/sdk/api/run-partial-flow.ts
297
+ function buildRequest(flowId, parts, params, asyncMode) {
298
+ const form = new FormData();
299
+ for (const part of parts) {
300
+ form.append("files", new File([part.data], part.filename, { type: part.contentType }));
301
+ }
302
+ if (params.context !== void 0) form.append("jsonContext", JSON.stringify(params.context));
303
+ form.append("executionScope", params.executionScope);
304
+ if (params.flowVersion !== void 0) form.append("flowVersion", params.flowVersion);
305
+ for (const name of params.classificationNodeNames ?? []) {
306
+ form.append("classificationNodeNames", name);
307
+ }
308
+ const suffix = asyncMode ? "partial-execute-async" : "partial-execute";
309
+ const path = `/flows/${flowId}/${suffix}`;
310
+ return {
311
+ path: params.organization ? `${path}?organization=${encodeURIComponent(params.organization)}` : path,
312
+ form
313
+ };
314
+ }
315
+ async function runPartialFlow(http, flowId, parts, params, signal) {
316
+ const { path, form } = buildRequest(flowId, parts, params, false);
317
+ return http.requestJson("POST", path, { body: form, signal });
318
+ }
319
+ async function runPartialFlowAsync(http, flowId, parts, params, signal) {
320
+ const { path, form } = buildRequest(flowId, parts, params, true);
321
+ if (params.callbackUrl !== void 0) form.append("callbackUrl", params.callbackUrl);
322
+ if (params.language !== void 0) form.append("language", params.language);
323
+ return http.requestJson("POST", path, {
324
+ body: form,
325
+ signal
326
+ });
327
+ }
328
+
285
329
  // src/sdk/status.ts
286
330
  function normalizeStatus(status, fallback = "UNKNOWN") {
287
331
  if (typeof status !== "string") return fallback;
@@ -333,6 +377,52 @@ async function getResult(http, executionResultId, signal, language, organization
333
377
  });
334
378
  }
335
379
 
380
+ // src/sdk/api/fraud-detection.ts
381
+ async function startFraudDetection(http, params, signal) {
382
+ return http.requestJson("POST", "/fraud-detections", {
383
+ query: organizationQuery(params.organization),
384
+ headers: { "content-type": "application/json" },
385
+ body: JSON.stringify({
386
+ documentUrl: params.documentUrl,
387
+ filename: params.filename,
388
+ ...params.analystContext === void 0 ? {} : { analystContext: params.analystContext }
389
+ }),
390
+ signal
391
+ });
392
+ }
393
+ async function getFraudDetectionStatus(http, jobId, signal, organization) {
394
+ const res = await http.requestRaw(
395
+ "GET",
396
+ `/fraud-detections/${encodeURIComponent(jobId)}/status`,
397
+ {
398
+ query: organizationQuery(organization),
399
+ signal
400
+ }
401
+ );
402
+ const text = await res.text();
403
+ const body = text ? JSON.parse(text) : { jobId, status: "" };
404
+ const normalized = {
405
+ ...body,
406
+ status: normalizeStatus(body.status)
407
+ };
408
+ const pollAfterMs = parsePositiveInt2(res.headers.get("x-poll-after"));
409
+ return pollAfterMs === void 0 ? normalized : { ...normalized, pollAfterMs };
410
+ }
411
+ async function getFraudDetectionResult(http, jobId, signal, organization) {
412
+ return http.requestJson("GET", `/fraud-detections/${encodeURIComponent(jobId)}/result`, {
413
+ query: organizationQuery(organization),
414
+ signal
415
+ });
416
+ }
417
+ function organizationQuery(organization) {
418
+ return organization ? { organization } : void 0;
419
+ }
420
+ function parsePositiveInt2(value) {
421
+ if (!value) return void 0;
422
+ const parsed = Number(value);
423
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
424
+ }
425
+
336
426
  // src/sdk/api/list-flows.ts
337
427
  async function listFlows(http, organization) {
338
428
  const path = organization ? `/flows?organization=${encodeURIComponent(organization)}` : "/flows";
@@ -477,11 +567,13 @@ async function pollUntilTerminal(opts) {
477
567
  try {
478
568
  result = normalizePollResult(await opts.check());
479
569
  } catch (err) {
480
- if (!(err instanceof DocmanaRateLimitError)) throw err;
570
+ const retryAfterMs = err instanceof DocmanaRateLimitError ? err.retryAfterMs ?? opts.intervalMs : opts.shouldRetryError?.(err) ? fullJitterDelay(opts.intervalMs, opts.maxIntervalMs, fallbackAttempt, random) : void 0;
571
+ if (retryAfterMs === void 0) throw err;
481
572
  if (elapsed() >= opts.timeoutMs) {
482
573
  throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
483
574
  }
484
- await sleep(err.retryAfterMs ?? opts.intervalMs);
575
+ fallbackAttempt += 1;
576
+ await sleep(retryAfterMs);
485
577
  continue;
486
578
  }
487
579
  const status = normalizeStatus(result.status);
@@ -535,6 +627,72 @@ var Docmana = class {
535
627
  params.organization
536
628
  );
537
629
  }
630
+ async detectFraudAsync(params, options = {}) {
631
+ return startFraudDetection(this.http, params, options.signal);
632
+ }
633
+ async getFraudDetectionStatus(jobId, signal, organization) {
634
+ return getFraudDetectionStatus(this.http, jobId, signal, organization);
635
+ }
636
+ async getFraudDetectionResult(jobId, signal, organization) {
637
+ return getFraudDetectionResult(this.http, jobId, signal, organization);
638
+ }
639
+ async detectFraud(params, options = {}) {
640
+ const { jobId } = await this.detectFraudAsync(params, { signal: options.signal });
641
+ let attempt = 0;
642
+ const startedAt = Date.now();
643
+ await pollUntilTerminal({
644
+ check: async () => {
645
+ attempt += 1;
646
+ try {
647
+ const statusResult = await this.getFraudDetectionStatus(
648
+ jobId,
649
+ options.signal,
650
+ params.organization
651
+ );
652
+ const nextPollAfterMs = isTerminalStatus(statusResult.status) ? void 0 : statusResult.pollAfterMs;
653
+ options.onPoll?.({
654
+ jobId,
655
+ status: statusResult.status,
656
+ attempt,
657
+ elapsedMs: Date.now() - startedAt,
658
+ nextPollAfterMs
659
+ });
660
+ return { status: statusResult.status, nextDelayMs: nextPollAfterMs };
661
+ } catch (err) {
662
+ if (err instanceof DocmanaRateLimitError) {
663
+ options.onRateLimit?.({
664
+ jobId,
665
+ attempt,
666
+ elapsedMs: Date.now() - startedAt,
667
+ retryAfterMs: err.retryAfterMs ?? options.pollIntervalMs ?? this.config.pollIntervalMs
668
+ });
669
+ }
670
+ throw err;
671
+ }
672
+ },
673
+ intervalMs: options.pollIntervalMs ?? this.config.pollIntervalMs,
674
+ maxIntervalMs: options.pollMaxIntervalMs ?? this.config.pollMaxIntervalMs,
675
+ timeoutMs: options.timeoutMs ?? this.config.timeoutMs,
676
+ signal: options.signal,
677
+ shouldRetryError: isTransientPollingError
678
+ });
679
+ const result = await this.getFraudDetectionResult(jobId, options.signal, params.organization);
680
+ if (isFailedStatus(result.status)) {
681
+ throw new DocmanaFraudDetectionError(
682
+ result.error?.message ?? `Fraud detection ${jobId} failed`,
683
+ result
684
+ );
685
+ }
686
+ return result;
687
+ }
688
+ async runPartialFlow(flowId, params, options = {}) {
689
+ const parts = await resolveInputs(params);
690
+ return runPartialFlow(this.http, flowId, parts, params, options.signal);
691
+ }
692
+ async runPartialFlowAsync(flowId, params, options = {}) {
693
+ const parts = await resolveInputs(params);
694
+ return runPartialFlowAsync(this.http, flowId, parts, params, options.signal);
695
+ }
538
696
  async getStatus(executionResultId, signal, organization) {
539
697
  return getStatus(this.http, executionResultId, signal, organization);
540
698
  }
@@ -623,6 +781,13 @@ var Docmana = class {
623
781
  return isTerminalStatus(status);
624
782
  }
625
783
  };
784
+ function isTransientPollingError(error) {
785
+ if (error instanceof DocmanaRateLimitError) return false;
786
+ if (error instanceof DocmanaError) {
787
+ return error.status !== void 0 && error.status >= 500;
788
+ }
789
+ return error instanceof TypeError;
790
+ }
626
791
 
627
792
  // src/cli/errors.ts
628
793
  var CliUsageError = class extends Error {