> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anyformat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK Guide

> Complete guide for the in-house anyformat TypeScript SDK

The hand-written TypeScript SDK for anyformat. The wire types and the low-level HTTP client are generated from the API's OpenAPI spec with [Hey API](https://heyapi.dev); the ergonomic layer (`Anyformat`, `Schema`, `WorkflowBuilder`, `Result`) is hand-written and mirrors the [Python SDK](/api-reference/sdks/python).

## Installation

Requires Node 18+.

```bash theme={null}
npm install @anyformat/sdk
```

The package ships ESM + CJS bundles and TypeScript types.

## Authentication

Pass the API key to the `Anyformat` constructor, or read it from the environment.

```typescript theme={null}
import { Anyformat } from "@anyformat/sdk";

// Explicit
const af = new Anyformat({ apiKey: "af_..." });

// Or from the environment (Node)
const af2 = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });
```

Override the base URL with `baseUrl` if you're running against a non-production deploy. See [Authentication](/api-reference/authentication) for how to mint an API key.

## Basic usage

Full flow: build a workflow with a fluent builder, submit a document, await the typed result.

```typescript theme={null}
import { Anyformat, Schema } from "@anyformat/sdk";

const af = new Anyformat({ apiKey: process.env.ANYFORMAT_API_KEY! });
const file: File = /* a File with .name set, e.g. new File([bytes], "invoice.pdf") */;

const workflow = await af
  .workflow("Invoice Processor", "Extract invoice header and totals")
  .parse()
  .extract([
    Schema.string("invoice_number", "The unique invoice identifier"),
    Schema.float("total_amount",    "Total invoice amount"),
    Schema.date("issue_date",       "Date when the invoice was issued"),
  ])
  .create();             // persists the workflow → returns a Workflow handle

const run = await workflow.run(file);   // submits the document
const result = await run.wait();        // polls /results/ until 200

console.log(result.field("invoice_number")?.value);
console.log(result.field("total_amount")?.value);
```

The TS SDK splits "create workflow" from "submit document", mirroring the Python SDK. `create()` returns a `Workflow` handle whose `run(file)` reuses the existing workflow id — so processing N documents through the same workflow costs one `POST /v2/workflows/` plus N `POST /v2/workflows/{id}/run/`, not N create+run pairs.

## `wait` options

`wait` defaults: 300s overall timeout, 3s poll interval. Override either:

```typescript theme={null}
const result = await run.wait({ timeoutMs: 180_000, pollMs: 3_000 });
```

`SDKTimeout` is thrown if the overall deadline expires before the run completes. The internal poll handles 412 (still processing) automatically.

## Builder methods

All node types in the [typed graph](/concepts/workflows) are exposed as fluent methods. `parse` is required; the others are optional and can be chained in any topology the API allows.

| Method                         | What it adds                | Notes                                                                                    |
| ------------------------------ | --------------------------- | ---------------------------------------------------------------------------------------- |
| `.parse(opts?)`                | The required parse node     | `opts = { mode: "standard" \| "agentic", promptHint?, figureEnhancement? }`              |
| `.classify(categories, opts?)` | A classify node             | Categories from `ClassifyCategory` objects (`id`, `name`, `description`)                 |
| `.split(rules, opts?)`         | A splitter node             | `opts.routeFrom` wires which classify branch fans out                                    |
| `.extract(fields, opts?)`      | An extract node             | `opts.branch` is required after `.classify()` / `.split()`; build fields with `Schema.*` |
| `.validate(rules, opts?)`      | A validate node             | Attaches to the most recent extract (or `opts.branch`)                                   |
| `.build()`                     | Same shape, no network call | Returns a `WorkflowCreateRequest`                                                        |
| `.create()`                    | Persists the workflow       | Returns a `Workflow` handle (id + `.run(...)`)                                           |

The `Workflow` handle returned by `.create()` has one method:

| Method              | What it does                                        | Notes                                                                     |
| ------------------- | --------------------------------------------------- | ------------------------------------------------------------------------- |
| `.run(file, opts?)` | Submits a document against the existing workflow id | `opts.text` to submit raw text instead of a file; pass `null` as the file |

`Workflow.run` returns a `Run`; `Run.wait(opts?)` returns a `Result` once processing finishes.

## Managing workflows

The client also lists and deletes workflows:

| Method                                            | What it does               | Returns                                                               |
| ------------------------------------------------- | -------------------------- | --------------------------------------------------------------------- |
| `af.listWorkflows({ page?, pageSize?, status? })` | One page of your workflows | `Promise<Workflow[]>` — each is `.run(...)`-able (`pageSize` max 100) |
| `af.deleteWorkflow(workflowId)`                   | Soft-deletes a workflow    | `Promise<string>` — the deleted id (a 404 throws `NotFound`)          |

```typescript theme={null}
const workflows = await af.listWorkflows({ pageSize: 50 });
for (const wf of workflows) console.log(wf.id, wf.name);

const file = new File([/* bytes */], "invoice.pdf");
const run = await workflows[0].run(file);
const result = await run.wait();

await af.deleteWorkflow(workflows[0].id);
```

## Reading results

```typescript theme={null}
const result = await run.wait();

// Scalar fields, for linear workflows (single untagged extraction)
const inv = result.field("invoice_number");
console.log(inv?.value, inv?.confidence, inv?.evidence);

// Anything else — nested objects, split workflows, classifications:
// read result.raw, the validated wire envelope.
const markdown = result.raw.parse?.markdown;
for (const extraction of result.extractions) {
  for (const [name, field] of Object.entries(extraction.fields)) {
    console.log(name, field);
  }
}
```

See [Response formats](/api-reference/response-formats) for the full shape of every section.

## Error handling

The SDK exports typed error classes:

```typescript theme={null}
import {
  AnyformatError,   // base class
  APIError,         // every HTTP failure that isn't a typed subclass
  BadRequest,       // 400 — validation error, bad request body
  Unauthorized,     // 401 — invalid or missing API key
  Forbidden,        // 403 — disallowed by the server
  NotFound,         // 404 — workflow / file / webhook does not exist
  RateLimited,      // 429 — slow down
  ServerError,      // 5xx — internal anyformat error
  SDKTimeout,       // local timeout — wait() exceeded timeoutMs
  WorkflowBuilderError, // builder-API misuse, e.g. .extract() before .parse()
} from "@anyformat/sdk";

try {
  const workflow = await af.workflow("…").parse().extract([/* … */]).create();
  const run = await workflow.run(file);
  const result = await run.wait();
} catch (err) {
  if (err instanceof RateLimited) {
    // back off, retry
  } else if (err instanceof NotFound) {
    // workflow or file is gone
  } else if (err instanceof APIError) {
    console.error(`HTTP ${err.status}`, err.body);
  } else if (err instanceof SDKTimeout) {
    // polling deadline exceeded
  } else {
    throw err;
  }
}
```

Wire-format error codes (e.g. `EXTRACTION_FAILED`, `RATE_LIMITED`) are documented at [Errors](/api-reference/errors). The `APIError.body` field holds the parsed JSON body the server returned.

## Webhooks (not on the SDK surface yet)

`Anyformat` doesn't expose webhook endpoints directly today. Until it does, register and delete webhooks with `fetch` or the generated low-level client:

```typescript theme={null}
const res = await fetch("https://api.anyformat.ai/v2/webhooks/", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANYFORMAT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://your-server.com/hook",
    events: ["extraction.completed"],
  }),
});
const webhook = await res.json();
// webhook.secret is only returned once — store it.
```

See [Webhooks](/api-reference/webhooks/overview) for the payload shape and signature verification.

## Links

* [npm](https://www.npmjs.com/package/@anyformat/sdk) — `npm install @anyformat/sdk`
* [Python SDK](/api-reference/sdks/python) — the same fluent shape in Python
* [Coding assistant](/guides/coding-assistant) — let Claude drive anyformat from your editor
