@docmana/sdk 0.19.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,12 +1,21 @@
1
- # Changelog
2
-
3
- ## 0.19.0 - 2026-08-04
4
-
5
- - Add synchronous and asynchronous V2 partial flow execution through Consumer Edge.
6
- - Use `executionScope`, `flowVersion`, and `classificationNodeNames` in the
7
- multipart body, leaving only `organization` in the query string.
8
- - Return the standard `DocmanaExecutionResult` shape with optional
9
- `partialExecution` metadata.
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.
10
19
 
11
20
  ## 0.18.0
12
21
  - Expose the optional aggregate `verdict` on `DocmanaExecutionResult`, preserving
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`.
@@ -128,12 +143,22 @@ use `RunFlowParams` for request data:
128
143
  - `runFlow(flowId, params, options?)` starts async execution, polls status, fetches the
129
144
  result, respects `X-Poll-After` and `429 Retry-After` while polling, and throws
130
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.
131
153
 
132
154
  Consumer is the polling authority. When `GET /status` returns `X-Poll-After`,
133
155
  the SDK waits that many milliseconds before the next status call. If the header
134
156
  is absent, the SDK falls back to capped exponential full jitter between
135
157
  `pollIntervalMs` and `pollMaxIntervalMs`. `Retry-After` on `429` always wins.
136
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
+
137
162
  ```ts
138
163
  const partial = await docmana.runPartialFlow("<flow-id>", {
139
164
  files: ["./contract.pdf"],
@@ -277,15 +302,16 @@ from the Bearer token.
277
302
  Every SDK error extends `DocmanaError`, which carries `.status`, `.code`, and an
278
303
  optional `.requestId` when available.
279
304
 
280
- | Error | Thrown when |
281
- | ------------------------ | -------------------------------------------------------------------------- |
282
- | `DocmanaRequestError` | `400` bad request. |
283
- | `DocmanaAuthError` | `401` rejected or expired Bearer token. |
284
- | `DocmanaPermissionError` | `403` missing permission. |
285
- | `DocmanaNotFoundError` | `404` flow or execution not found. |
286
- | `DocmanaExecutionError` | The flow finished with status `"FAILED"`. Carries `.errors` and `.result`. |
287
- | `DocmanaTimeoutError` | Polling exceeded `timeoutMs`. |
288
- | `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. |
289
315
 
290
316
  ## Local development with yalc
291
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>;
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" });
@@ -366,6 +377,52 @@ async function getResult(http, executionResultId, signal, language, organization
366
377
  });
367
378
  }
368
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
+
369
426
  // src/sdk/api/list-flows.ts
370
427
  async function listFlows(http, organization) {
371
428
  const path = organization ? `/flows?organization=${encodeURIComponent(organization)}` : "/flows";
@@ -510,11 +567,13 @@ async function pollUntilTerminal(opts) {
510
567
  try {
511
568
  result = normalizePollResult(await opts.check());
512
569
  } catch (err) {
513
- 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;
514
572
  if (elapsed() >= opts.timeoutMs) {
515
573
  throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
516
574
  }
517
- await sleep(err.retryAfterMs ?? opts.intervalMs);
575
+ fallbackAttempt += 1;
576
+ await sleep(retryAfterMs);
518
577
  continue;
519
578
  }
520
579
  const status = normalizeStatus(result.status);
@@ -568,6 +627,64 @@ var Docmana = class {
568
627
  params.organization
569
628
  );
570
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
+ }
571
688
  async runPartialFlow(flowId, params, options = {}) {
572
689
  const parts = await resolveInputs(params);
573
690
  return runPartialFlow(this.http, flowId, parts, params, options.signal);
@@ -664,6 +781,13 @@ var Docmana = class {
664
781
  return isTerminalStatus(status);
665
782
  }
666
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
+ }
667
791
 
668
792
  // src/cli/errors.ts
669
793
  var CliUsageError = class extends Error {