@docmana/sdk
v0.25.0
Published
Official Docmana SDK — run document-analysis flows with a single call.
Downloads
2,044
Keywords
Readme
@docmana/sdk
Official Docmana SDK for calling docmana-consumer-edge-api.
The SDK sends documents to Consumer, starts an async flow, polls Consumer for status, and returns the mapped Consumer execution DTO.
Installation
npm install @docmana/sdkRequires Node.js 20+.
Quickstart
import { Docmana } from "@docmana/sdk";
const docmana = new Docmana({
apiBaseUrl: process.env.DOCMANA_API_URL!,
accessToken: process.env.DOCMANA_ACCESS_TOKEN!,
});
const result = await docmana.runFlow("<flow-id>", {
files: ["./contract.pdf"],
context: { contract: { destination: "Canada" } },
useDraftVersion: true,
});
console.log(result.status, result.executionId);Fraud detection follows the same asynchronous Consumer Edge pattern. The high-level call starts the job, polls Consumer, and returns the normalized result:
const fraud = await docmana.detectFraud({
file: "./claim.pdf",
analystContext: "Travel claim; verify the invoice total.",
});
console.log(fraud.verdict, fraud.summary.highestSeverity);
console.log(fraud.summary, fraud.findings);Authentication
The public SDK is Bearer-only. It does not acquire OAuth tokens and it never
calls docmana-api directly. Provide either a static accessToken or a
getAccessToken provider:
const docmana = new Docmana({
apiBaseUrl: "https://consumer.example.com/v1",
getAccessToken: async ({ forceRefresh } = {}) => {
return forceRefresh ? refreshTokenSomehow() : currentToken();
},
});If Consumer returns 401 and getAccessToken is configured, the SDK calls
getAccessToken({ forceRefresh: true }) once and retries the request. With a
static accessToken, 401 is returned as an auth error without retry.
Configuration
| Field | Type | Required | Description |
| ------------------- | --------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------- |
| apiBaseUrl | string | Yes | Base URL of Consumer API, including version when applicable. |
| accessToken | string | One of | Static Bearer token sent to Consumer. |
| getAccessToken | (options?: { forceRefresh?: boolean }) => string \| Promise<string> | One of | Token provider for renewable Bearer tokens. |
| timeoutMs | number | No | Maximum polling time for runFlow and detectFraud. Default: 5 minutes. |
| pollIntervalMs | number | No | Fallback minimum delay between status polls. Default: 2s. |
| pollMaxIntervalMs | number | No | Fallback maximum polling delay. Default: 10s. |
| fetch | typeof fetch | No | Custom fetch implementation. |
| headers | Record<string, string> | No | Extra headers sent on every Consumer request. |
apiBaseUrl is required so a migrated SDK cannot accidentally call
https://api.docmana.ai.
Run flow params
runFlow(flowId, params, options) and runFlowAsync(flowId, params, options)
use RunFlowParams for request data:
| Field | Type | Description |
| ----------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| files | (string \| { path: string } \| { data, filename, contentType? })[] | Documents to upload. A string or { path } is treated as a file path. |
| context | Record<string, unknown> | Business context serialized as multipart jsonContext. |
| language | string | Optional result language passed to Consumer. |
| useDraftVersion | boolean | Runs the current draft flow version. |
| callbackUrl | string | Async-only callback URL, serialized as callbackUrl. |
runFlow uses RunFlowOptions for local execution controls:
| Field | Type | Description |
| ------------------- | ----------------- | ------------------------------------------------------------ |
| signal | AbortSignal | Cancels the in-flight request/polling. |
| timeoutMs | number | Overrides configured polling timeout for this call. |
| pollIntervalMs | number | Overrides configured fallback minimum polling interval. |
| pollMaxIntervalMs | number | Overrides configured fallback maximum polling interval. |
| onPoll | (event) => void | Called after each status poll. |
| onRateLimit | (event) => void | Called when polling receives 429; includes retryAfterMs. |
runFlowAsync accepts RunFlowAsyncOptions with signal.
once was removed. Use useDraftVersion.
Methods
listFlows()callsGET /v1/flowsand returns{ id, name, state }[]for the flows available to the Bearer token. Missing flow states are returned as"unknown".listOrganizations()callsGET /v1/organizationsand returns{ id, name }[]for the organizations available to the Bearer token.getFlowMetadata(flowId)callsGET /v1/flows/:flowId/metadataand returns{ flowId, name?, jsonContextSchema, languages, minScore?, documents }.getFlowDefinition(flowId)callsGET /v1/flows/:flowId/definitionand returns the normalized flow definition{ flowId, name?, languages, minScore?, nodes }.listOrganizationDocuments({ prefix? })callsGET /v1/organization-documentsand returns{ configured, documents }.downloadOrganizationDocument(path)callsGET /v1/organization-documents/download?path=...and returns{ bytes, filename, contentType? }.downloadOrganizationDocuments(paths)downloads each requested path.runFlowAsync(flowId, params, options?)posts multipart files toPOST /v1/flows/:flowId/execute-asyncand returns{ executionResultId, fileIds }.runPartialFlow(flowId, params, options?)executes classification, extraction, or extraction with validation synchronously through Consumer Edge. It returns the standardDocmanaExecutionResultwithpartialExecutionmetadata.runPartialFlowAsync(flowId, params, options?)starts the same partial execution asynchronously and returns{ executionResultId, fileIds }for standard polling.getStatus(executionResultId)callsGET /v1/executions/:executionResultId/status.getResult(executionResultId, signal?, language?)callsGET /v1/executions/:executionResultId/result; its result includes the optionalflowVersionidentifying the exact flow version used by the execution. Processing steps can also includemodelandpromptIdprovenance when Docmana reports them.runFlow(flowId, params, options?)starts async execution, polls status, fetches the result, respectsX-Poll-Afterand429 Retry-Afterwhile polling, and throwsDocmanaExecutionErrorif the result status isFAILED.detectFraud(params, options?)starts fraud detection from eitherdocumentUrlplusfilename, or a localfileusing the standardFileInputcontract. It polls Consumer untilCOMPLETEDorFAILEDand returns the normalized verdict, aggregatesummaryandfindingswith job-local references. Each finding keeps its analysis category inlayer. Internal preprocessing, layer telemetry, token counts, LLM calls, model usage, costs, persistence diagnostics, and the sensitive Frauddocumentpayload are intentionally never exposed by Consumer or SDK.detectFraudAsync(params, options?)starts fraud detection and immediately returns{ jobId, status }without polling.getFraudDetectionStatus(jobId, signal?)andgetFraudDetectionResult(jobId, signal?)provide the lower-level polling workflow.
Consumer is the polling authority. When GET /status returns X-Poll-After,
the SDK waits that many milliseconds before the next status call. If the header
is absent, the SDK falls back to capped exponential full jitter between
pollIntervalMs and pollMaxIntervalMs. Retry-After on 429 always wins.
Path inputs are streamed from disk instead of being loaded completely into memory.
For multipart submissions only, the SDK retries one 503 Service Unavailable
response carrying Retry-After with a fresh request body, respecting that delay
up to 30 seconds. A downstream 503 without Retry-After is not retried.
Payloads rejected with 413 Payload Too Large are never retried.
The SDK does not log or return documentUrl or uploaded file contents. Signed
query parameters are sent only in the JSON request body to Consumer Edge. Fraud
jobs are not organization-scoped; the legacy organization inputs are accepted
but ignored for compatibility with SDK 0.20 callers.
URL and local-file inputs use the same methods:
const fromUrl = await docmana.detectFraudAsync({
documentUrl: "https://downloads.example.com/claim.pdf?token=...",
filename: "claim.pdf",
});
const fromFile = await docmana.detectFraudAsync({
file: { path: "./claim.pdf" },
analystContext: "Check the invoice total.",
});const partial = await docmana.runPartialFlow("<flow-id>", {
files: ["./contract.pdf"],
executionScope: "extraction_and_validation",
flowVersion: "published",
classificationNodeNames: ["classificationContract"],
});
console.log(partial.documents, partial.partialExecution);const flows = await docmana.listFlows();
console.log(flows.map((flow) => `${flow.id}: ${flow.name} (${flow.state})`).join("\n"));const organizations = await docmana.listOrganizations();
console.log(organizations.map((org) => `${org.id}: ${org.name}`).join("\n"));const metadata = await docmana.getFlowMetadata("<flow-id>");
console.log(metadata.jsonContextSchema);
const definition = await docmana.getFlowDefinition("<flow-id>");
console.log(definition.nodes.map((node) => node.label).join("\n"));const depot = await docmana.listOrganizationDocuments({ prefix: "folder/" });
if (depot.configured) {
const file = await docmana.downloadOrganizationDocument(depot.documents[0].path);
console.log(file.filename, file.bytes.byteLength);
}const { executionResultId } = await docmana.runFlowAsync("<flow-id>", {
files: ["./contract.pdf"],
});
let status = "PENDING";
while (status !== "COMPLETED" && status !== "FAILED") {
await new Promise((r) => setTimeout(r, 2000));
({ status } = await docmana.getStatus(executionResultId));
}
const result = await docmana.getResult(executionResultId, undefined, "fr");Webhook callbacks
runFlowAsync(flowId, params, options?) accepts callbackUrl and sends it as
callbackUrl. It returns immediately and does not poll.
const { executionResultId } = await docmana.runFlowAsync("<flow-id>", {
files: ["./contract.pdf"],
callbackUrl: "https://your-service.example.com/webhooks/docmana/executions",
language: "fr",
});Consumer posts the mapped result DTO to your callback URL when the execution is
finished. Keep executionResultId if you also want to poll as a fallback.
CLI
The CLI can still acquire and cache a token through OAuth:
docmana config init
docmana logindocmana login reads clientId, clientSecret, tokenEndpoint, and scope
from flags, environment variables, or docmana.config.json, then stores the
access token in docmana.token.json.
docmana flow run always passes a Bearer token to the SDK. It resolves the token
in this order:
--access-tokenDOCMANA_ACCESS_TOKEN- valid
docmana.token.json - OAuth refresh using CLI/config credentials
DOCMANA_API_URL="https://consumer.example.com/v1"
docmana flow listDOCMANA_API_URL="https://consumer.example.com/v1"
docmana organization listDOCMANA_API_URL="https://consumer.example.com/v1"
docmana organization document list --prefix "folder/"
docmana organization document download "folder/invoice.pdf"
docmana organization document download "folder/invoice.pdf" --output "./invoice.pdf"DOCMANA_API_URL="https://consumer.example.com/v1"
docmana flow metadata "<flow-id>"
docmana flow definition "<flow-id>"DOCMANA_API_URL="https://consumer.example.com/v1"
docmana flow run "<flow-id>" --files "./contract.pdf"You can also run without the cache:
docmana flow run "<flow-id>" \
--api-url "https://consumer.example.com/v1" \
--access-token "$DOCMANA_ACCESS_TOKEN" \
--files "./contract.pdf,./appendix.pdf" \
--context '{"contract":{"destination":"Canada"}}' \
--draft \
--jsonUse --draft to run the current draft flow version.
Use --context to pass business context as a JSON object.
Use docmana flow list --json to print the raw flow list returned by Consumer.
Use docmana organization list --json to print the organization list as JSON.
Use docmana organization document list --json for raw organization document JSON.
docmana flow metadata and docmana flow definition print JSON by default.
The CLI no longer sends X-Selected-Organization; Consumer derives identity
from the Bearer token.
Error handling
Every SDK error extends DocmanaError, which carries .status, .code, and an
optional .requestId when available.
| Error | Thrown when |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| DocmanaRequestError | 400 bad request. |
| DocmanaPayloadTooLargeError | 413 multipart payload exceeds a Consumer limit. |
| DocmanaAuthError | 401 rejected or expired Bearer token. |
| DocmanaPermissionError | 403 missing permission. |
| DocmanaNotFoundError | 404 flow or execution not found. |
| DocmanaExecutionError | The flow finished with status "FAILED". Carries .errors and .result. |
| DocmanaFraudDetectionError | Fraud detection finished with status "FAILED". Carries .jobId, .error, and .result. |
| DocmanaTimeoutError | Polling exceeded timeoutMs. |
| DocmanaAbortError | The provided AbortSignal fired. |
| DocmanaServiceUnavailableError | 503 Consumer upload capacity is unavailable after the bounded retry. Carries .retryAfterMs. |
Local development with yalc
In this library:
npm run devIn the consuming project:
yalc add @docmana/sdk
npm installFor a one-shot publish to the local yalc store:
npm run yalc:publishLicense
MIT
