> ## 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.

# SDK migration: 0.x to 1.0

> Upgrade the AnyFormat Python and TypeScript SDKs from the v2-based 0.x releases to the v3-based 1.0 release.

The 1.0 SDKs use [API v3](/api-reference-v3/introduction). Workflow authoring and the results payload remain familiar, but uploads, runs, pagination, and workflow updates now follow v3's resource model.

This guide covers both official packages:

* Python: `anyformat` 0.x → 1.x
* TypeScript: `@anyformat/sdk` 0.x → 1.x

<Info>
  The 0.x packages continue to use API v2. Do not upgrade the package without making the code changes below. For the HTTP-level endpoint changes and the v2 sunset schedule, see [Migrating from v2](/api-reference-v3/migrating-from-v2).
</Info>

## What changes in 1.0

| 0.x                                                        | 1.0                                                                     |
| ---------------------------------------------------------- | ----------------------------------------------------------------------- |
| Uploads expose `file_id` or `collection_id`                | Uploads expose one `document_packet_id` and a list of files             |
| A run handle is effectively a collection id                | A run has its own `run_id`, status, workflow id, and document packet id |
| `wait()` polls results and treats HTTP 412 as "not ready"  | `wait()` polls the run until `processed`, `error`, or `cancelled`       |
| Lists use `page`, `page_size`, and optional status filters | Lists use `limit` and an opaque `cursor`                                |
| Workflow updates use v2 `PUT`                              | Workflow updates use v3 `PATCH` and preserve field `persistent_id`s     |
| A staged file can be run by its file id                    | A document packet is run by its packet id                               |

Authentication, API keys, the fluent workflow schema, standalone parse helpers, and the extraction results envelope do not require a conceptual migration. The standalone parse helpers still use the published v2 parse operation because v3 does not yet have an equivalent.

## Install 1.0

The 1.0 Python package requires Python 3.10 or later. The TypeScript package requires Node.js 18 or later.

```bash Python theme={null}
python -m pip install --upgrade "anyformat>=1,<2"
```

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

During the 1.0 release-candidate period, opt in to the prerelease explicitly:

```bash Python theme={null}
python -m pip install --pre --upgrade "anyformat>=1.0.0rc1,<2"
```

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

Commit the updated lockfile, then run your type checker and tests. The removed methods and renamed properties are intentional compile-time migration signals.

## Python

### Creating and running a workflow

The common create-and-run flow needs little change. In 1.0, keep the returned `Run` if you need its status or identifiers.

```python 0.x theme={null}
workflow = (
    client.workflow("Invoices")
    .parse()
    .extract(fields)
    .create()
)
result = workflow.run("invoice.pdf").wait()
```

```python 1.0 theme={null}
workflow = (
    client.workflow("Invoices")
    .parse()
    .extract(fields)
    .create()
)
run = workflow.run("invoice.pdf")
print(run.id, run.document_packet_id, run.status)
result = run.wait()
```

`run.wait()` now reads the flat v3 run resource. It returns a `Result` on `processed`, raises `ExtractionFailed` on `error`, and raises `ExtractionCancelled` on `cancelled`. Both terminal exceptions subclass `RunFailed`.

### Staged uploads and document packets

Replace upload `collection_id` and `file_id` access with `document_packet_id` and `files`.

```python 0.x theme={null}
upload = workflow.upload("invoice.pdf")
print(upload.collection_id, upload.file_id)
run = upload.run()
```

```python 1.0 theme={null}
upload = workflow.upload("invoice.pdf")
print(upload.document_packet_id)
for packet_file in upload.files:
    print(packet_file.id, packet_file.name)

run = upload.run()
```

A packet can contain between one and ten files. They are processed together as one document:

```python theme={null}
run = workflow.run(files=["invoice.pdf", "annex.pdf"])
```

If you stored a 0.x collection id, it identifies the same underlying resource in v3; rename it to `document_packet_id` in your application. Individual file ids inside a packet are not runnable handles.

### Removed file-centric methods

Replace `list_files()` with packet listing, and replace `run_workflow(workflow_id, file_ids)` with a packet run.

```python 0.x theme={null}
files = client.list_files(workflow.id)
runs = client.run_workflow(workflow.id, [files[0].id])
result = runs[0].wait()
```

```python 1.0 theme={null}
packets = client.list_document_packets(workflow.id)
packet = packets.items[0]
run = client.run_document_packet(packet.id)
result = run.wait()
```

Use `get_document_packet()`, `delete_document_packet()`, `get_run()`, and `list_runs()` when you need to manage those resources directly.

### Pagination

All list methods now return `Page(items, next_cursor)`. Page numbers, totals, `page_size`, and the list-time `status` filter are gone.

```python 0.x theme={null}
workflows = client.list_workflows(page=1, page_size=50, status="active")
for workflow in workflows:
    print(workflow.id)
```

```python 1.0 theme={null}
page = client.list_workflows(limit=50)
for workflow in page.items:
    print(workflow.id)

while page.next_cursor is not None:
    page = client.list_workflows(limit=50, cursor=page.next_cursor)
    for workflow in page.items:
        print(workflow.id)
```

For a full lazy traversal, prefer the 1.0 iterators:

```python theme={null}
for workflow in client.iter_workflows(limit=50):
    print(workflow.id)

for packet in client.iter_document_packets(workflow_id):
    ...

for run in client.iter_runs(workflow_id):
    ...
```

The async client has equivalent async iterators.

### Updating a workflow

Fetch the v3 definition, mutate it, and send it back. Preserve every existing field's `persistent_id`, including when renaming a field, so analytics and ground truth stay attached.

```python theme={null}
definition = client.get_workflow(workflow_id)

for node in definition.nodes:
    if node.type != "extract":
        continue
    for field in node.extraction_schema.fields:
        if field.name == "vendor":
            field.name = "vendor_name"  # persistent_id remains unchanged

client.update_workflow(workflow_id, definition)
```

Omit `persistent_id` only for a new field. The builder's `.update(workflow_id)` now uses the same v3 PATCH behavior.

### Errors

`NoResultsYet` has been removed because run status is explicit. Update terminal-run handling as follows:

```python 1.0 theme={null}
from anyformat.sdk import RunFailed, SDKTimeout

try:
    result = run.wait()
except RunFailed as exc:
    print(exc.run_id, exc.status)  # error or cancelled
except SDKTimeout:
    # The run can still finish; retrieve it later with client.get_run(run.id).
    raise
```

HTTP exceptions now expose `retryable` and `request_id`. A 402 response raises `PaymentRequired`. The existing `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `RateLimited`, `ServerError`, and `SDKTimeout` names remain valid.

### Smart lookup fields

Replace the 0.x `lookup=True` flag with an explicit field source:

```python 0.x theme={null}
Schema.string("vendor_id", "Canonical vendor id.", lookup=True)
```

```python 1.0 theme={null}
Schema.string("vendor_id", "Canonical vendor id.", source="smart_lookup")
```

Use `source="lookup_if_missing"` when extraction should run first and lookup should only fill an empty value.

## TypeScript

### Create the workflow before running it

In 0.x, `WorkflowBuilder.run()` could create and run in one chain. In 1.0, persist the workflow with `.create()`, then run the returned `Workflow`.

```ts 0.x theme={null}
const result = await af
  .workflow("Invoices")
  .parse()
  .extract(fields)
  .run(file)
  .wait();
```

```ts 1.0 theme={null}
const workflow = await af
  .workflow("Invoices")
  .parse()
  .extract(fields)
  .create();

const run = await workflow.run(file);
const result = await run.wait();
```

The 1.0 `Run` exposes `id`, `workflowId`, `documentPacketId`, and `status`. `Run.wait()` polls that run id instead of treating HTTP 412 as an in-progress result.

### Staged uploads and document packets

```ts 0.x theme={null}
const upload = await workflow.upload(file);
console.log(upload.collectionId, upload.fileId);
const run = await upload.run();
```

```ts 1.0 theme={null}
const upload = await workflow.upload(file);
console.log(upload.documentPacketId, upload.files);
const run = await upload.run();
```

Pass an array to create a multi-file packet:

```ts theme={null}
const run = await workflow.run([invoiceFile, annexFile]);
```

`Anyformat.runWorkflow(workflowId, fileIds)` has been removed. Run a staged packet instead:

```ts theme={null}
const run = await af.runDocumentPacket(documentPacketId);
```

Packet management is available through `getDocumentPacket()`, `listDocumentPackets()`, and `deleteDocumentPacket()`. Run management is available through `getRun()` and `listRuns()`.

### Pagination

List calls now return `{ items, nextCursor }` and accept `{ limit, cursor }`.

```ts 0.x theme={null}
const workflows = await af.listWorkflows({ page: 1, pageSize: 50, status: "active" });
for (const workflow of workflows) console.log(workflow.id);
```

```ts 1.0 theme={null}
let cursor: string | undefined;
do {
  const page = await af.listWorkflows({ limit: 50, cursor });
  for (const workflow of page.items) console.log(workflow.id);
  cursor = page.nextCursor ?? undefined;
} while (cursor);
```

For full traversal, use `iterWorkflows()`, `iterDocumentPackets(workflowId)`, or `iterRuns(workflowId)`:

```ts theme={null}
for await (const workflow of af.iterWorkflows({ limit: 50 })) {
  console.log(workflow.id);
}
```

### Result and error names

Rename `result.collectionId` to `result.documentPacketId`.

The deprecated unsuffixed error aliases have been removed. Import the `Error`-suffixed classes:

| Removed 0.x alias | 1.0 class           |
| ----------------- | ------------------- |
| `BadRequest`      | `BadRequestError`   |
| `Unauthorized`    | `UnauthorizedError` |
| `Forbidden`       | `ForbiddenError`    |
| `NotFound`        | `NotFoundError`     |
| `RateLimited`     | `RateLimitedError`  |
| `SDKTimeout`      | `SDKTimeoutError`   |

Handle terminal run outcomes through `RunFailedError` or its more specific subclasses:

```ts theme={null}
import { RunFailedError, SDKTimeoutError } from "@anyformat/sdk";

try {
  const result = await run.wait();
} catch (error) {
  if (error instanceof RunFailedError) {
    console.error(error.runId, error.status); // error or cancelled
  } else if (error instanceof SDKTimeoutError) {
    // The run can still finish; retrieve it later with af.getRun(run.id).
  } else {
    throw error;
  }
}
```

`APIError` additionally exposes `errorCode`, `retryable`, and `requestId`; 402 responses use `PaymentRequiredError`.

### Updating a workflow

Use the typed GET → edit → PATCH round trip, preserving `persistent_id`:

```ts theme={null}
const definition = await af.getWorkflow(workflowId);

for (const node of definition.nodes) {
  if (node.type !== "extract") continue;
  for (const field of node.extraction_schema.fields) {
    if (field.name === "vendor") field.name = "vendor_name";
  }
}

await af.updateWorkflow(workflowId, definition);
```

The builder's `.update(workflowId)` also uses PATCH in 1.0.

### Smart lookup fields

```ts 0.x theme={null}
Schema.string("vendor_id", "Canonical vendor id.", { lookup: true });
```

```ts 1.0 theme={null}
Schema.string("vendor_id", "Canonical vendor id.", { source: "smart_lookup" });
```

Use `source: "lookup_if_missing"` to retain extraction as the primary source.

## Add idempotency to retries

1.0 supports idempotency keys on uploads and run triggers. Use a stable, operation-specific key whenever your application retries after a timeout.

```python Python theme={null}
run = workflow.run(
    "invoice.pdf",
    idempotency_key="invoice-2026-07-20-upload-and-run",
)
```

```ts TypeScript theme={null}
const run = await workflow.run(file, {
  idempotencyKey: "invoice-2026-07-20-upload-and-run",
});
```

Reusing the same key for the same operation replays the original response instead of creating another packet or billable run.

## Migration checklist

* Upgrade the runtime if needed: Python 3.10+ or Node.js 18+.
* Upgrade the package and regenerate the lockfile.
* TypeScript: add `.create()` before running a newly built workflow.
* Rename collection/file handles to document packet handles.
* Replace `listFiles`/`list_files` and `runWorkflow`/`run_workflow` with packet APIs.
* Replace page-number pagination with `limit`/`cursor`, or use the new iterators.
* Preserve `persistent_id` when editing existing workflow fields.
* Update terminal-run and TypeScript error handling.
* Replace smart-lookup `lookup` flags with `source`.
* Add idempotency keys to retried uploads and run triggers.
* Exercise create, upload, run, wait, list, and workflow-update paths in a staging environment.
