@anyformat/sdk
v0.2.3
Published
TypeScript SDK for the AnyFormat API
Keywords
Readme
@anyformat/sdk
TypeScript SDK for the AnyFormat API. Mirrors the Python SDK's fluent flow.
import { Anyformat, Schema } from "@anyformat/sdk";
const af = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });
const result = await af
.workflow("Invoice")
.parse()
.extract([
Schema.string("vendor", "Vendor name on the invoice."),
Schema.float("total", "Grand total amount."),
Schema.enum("currency", "Currency of the totals.", [
Schema.option("USD", "US Dollar."),
Schema.option("EUR", "Euro."),
]),
])
.run(file)
.wait();
result.field("vendor")?.value; // -> "Acme Corp"Quick parse: one call → markdown
af.parse() runs the platform's atomic /parse operation (a fast lite parse)
and returns an opaque jobId. Poll getParseMarkdown(jobId) for the markdown —
no workflow to author or manage.
const jobId = await af.parse(file); // File | Blob
let markdown = await af.getParseMarkdown(jobId);
while (markdown === null) {
await new Promise((r) => setTimeout(r, 3000));
markdown = await af.getParseMarkdown(jobId); // null until done
}
console.log(markdown);Managing workflows
const workflows = await af.listWorkflows({ pageSize: 50 }); // -> Workflow[]
for (const wf of workflows) console.log(wf.id, wf.name);
await af.deleteWorkflow(workflows[0].id); // -> the deleted idSmart lookup
Resolve a field by matching the document against a reference file (CSV/catalog) instead of
reading it off the page — flag the field with { lookup: true } and pass the file via
lookupFiles:
await af
.workflow("Invoice + vendor lookup")
.parse()
.extract(
[
Schema.string("vendor_name", "Vendor as printed on the invoice."),
Schema.string("vendor_id", "Canonical vendor code from the catalog, joined on vendor_name.", { lookup: true }),
],
{ lookupFiles: ["vendor_catalog.csv"], lookupSuggestion: "Join vendor_name → vendor_id." },
)
.run(file)
.wait();Full runnable example: examples/smart-lookup.mjs.
How it's built
The wire types and the low-level HTTP client are generated from the API's
OpenAPI spec with Hey API — they are not hand-written and
must not be edited. Everything in src/sdk/ is generated; the hand-written
ergonomic layer (Schema, WorkflowBuilder, Result, Anyformat) wraps it.
Regenerating (and the drift guard)
bun run gen # dump the v2 OpenAPI offline + regenerate src/sdk
bun run gen:check # regenerate and fail if the committed output driftedgen:check runs in CI and as a pre-commit hook (scoped to changes under
anyformat/services/api and anyformat/workflow) so the committed SDK can
never fall behind the backend. The SDK's own tsc then fails to compile if a
backend model change alters a type the hand-written layer relies on — drift is
caught as a type error, not just a diff.
Scripts
Install deps with bun install --frozen-lockfile (matches CI + the frontend
toolchain). Then:
| script | what |
| --- | --- |
| bun run gen | dump spec + regenerate src/sdk |
| bun run gen:check | regenerate + assert no drift |
| bun run build | bundle to dist/ (ESM + CJS + types) via tsup |
| bun run typecheck | tsc --noEmit |
| bun run test | vitest |
Releasing
Cut on demand — a maintainer bumps the version in a PR, then pushes a sdk-js-vX.Y.Z tag on the release commit. Full flow + one-time setup: ../RELEASING.md. Break-glass: ./publish.sh [--build-only|--dry-run].
Node configuration knobs
Every knob is an option on .parse({...}) / .extract(fields, {...}). parse options are a
mode-discriminated union, so a wrong-mode knob is a compile-time error.
.parse({...}) — mode ("standard"/"agentic"/"lite"), promptHint, figureEnhancement,
cache; agentic-only effort ("low"/"mid"/"accurate"); lite-only ocrEffort
("medium"/"high"), region ("eu"/"global"), skipRoutingReview, figures,
textFormatting, routingEffort.
.extract(fields, {...}) — mode ("standard"/"agentic"/"max"/"lite"), useImages,
lookupSuggestion, lookupReasoningEffort ("minimal"/"low"/"medium"/"high"), lookupFiles.
