Skip to main content

Python SDK for the AnyFormat API

Project description

anyformat

Python SDK for the AnyFormat API. Build a document workflow with a fluent builder, run a file through it, and read typed results — including drawing the parser's layout boxes back onto the page.

pip install anyformat
from anyformat.sdk import Client
from anyformat.workflow import Schema

client = Client(api_key="af_...")  # or set ANYFORMAT_API_KEY

workflow = (
    client.workflow("Invoices")
    .parse()
    .extract([Schema.string("vendor", "Vendor name on the invoice.")])
    .create()
)

result = workflow.run("invoice.pdf").wait()
print(result.fields["vendor"].value)        # -> "Acme Corp"
result.parse.draw("out/")                    # -> out/page_1.png with boxes drawn

The client reads ANYFORMAT_API_KEY from the environment if you omit api_key. base_url defaults to https://api.anyformat.ai.


Concepts in 30 seconds

  • client.workflow(name) returns a fluent builder. Chain verbs (.parse(), .classify(), .split(), .extract(), .validate()) then .create() to register it and get a Workflow.
  • workflow.run(file) uploads a file and returns a Run. run.wait() polls until processing finishes and returns a Result.
  • client.list_workflows() returns a page of Workflow handles (each .run()-able); client.delete_workflow(id) soft-deletes one and returns its id.
  • result.fields is the extracted data; result.parse is the parser's output (markdown + per-block layout); result.raw is the full response.
  • Each node's behaviour lives on its own result view — draw() is a method of result.parse because only the parser produces layout boxes.

Cookbook

Every recipe below is a complete, runnable script. They share this header:

from anyformat.sdk import Client
from anyformat.workflow import Schema, ClassifyCategory, SplitterRule, ValidationRule

client = Client(api_key="af_...")

1. Parse a document → markdown + blocks

A parse-only workflow turns any document into structured markdown plus a typed list of layout blocks (each with a bounding box, type, and confidence).

workflow = client.workflow("Plain parse").parse().create()
result = workflow.run("contract.pdf").wait()

print(result.parse.markdown)          # full document as markdown
print(result.parse.text)              # markdown with HTML/anchors stripped

for block in result.parse.blocks:
    print(block.page, block.type, block.parse_confidence, block.bbox)

2. Visualize the bounding boxes

result.parse.draw() renders every block's box onto its page and writes one PNG per page. Boxes are coloured by parse_confidence, matching the Studio: ≥80 emerald · 50–79 amber · <50 rose · unknown blue.

workflow = client.workflow("Parse + draw").parse().create()
result = workflow.run("invoice.pdf").wait()

paths = result.parse.draw("out/")     # ["out/page_1.png", "out/page_2.png", ...]
print(paths)

The page background is the document you ran — it's retained automatically, so you don't hand the file back. Options:

# Override the background (path or bytes) — useful if you ran from raw bytes:
result.parse.draw("out/", source="invoice.pdf")

# Higher-resolution raster:
result.parse.draw("out/", dpi=300)

# No source available → boxes are drawn on blank canvases (layout map only).

PDFs are rasterized per page; image files (PNG/JPG/TIFF) are drawn on directly.

3. Extract fields (linear parse → extract)

Schema is the field factory. Mix any field types in one extract.

workflow = (
    client.workflow("Invoice extraction")
    .parse()
    .extract([
        Schema.string("vendor", "Vendor name on the invoice."),
        Schema.float("total", "Grand total amount."),
        Schema.date("issued_on", "Invoice issue date."),
        Schema.boolean("is_paid", "Whether the invoice is marked paid."),
        Schema.integer("line_count", "Number of line items."),
        Schema.enum("currency", "Currency of the totals.", options=[
            Schema.option("USD", "US Dollar."),
            Schema.option("EUR", "Euro."),
        ]),
        Schema.object("line_items", "One row per line item.", fields=[
            Schema.string("sku", "Stock keeping unit."),
            Schema.string("description", "Line description."),
            Schema.float("amount", "Line amount."),
        ]),
    ])
    .create()
)

result = workflow.run("invoice.pdf").wait()
field = result.fields["vendor"]
print(field.value, field.confidence, [e.text for e in field.evidence])

Field types: string, integer, float, boolean, date, datetime, enum, multi_select, object (nested).

4. Classify, then extract per category

classify() adds a branching node. Each extract() after it must declare which category routes into it via branch= (the category id, or the object itself).

invoice = ClassifyCategory(id="INVOICE", name="Invoice", description="A vendor invoice.")
receipt = ClassifyCategory(id="RECEIPT", name="Receipt", description="A point-of-sale receipt.")

workflow = (
    client.workflow("Invoice or Receipt")
    .parse()
    .classify(invoice, receipt)
    .extract([Schema.string("vendor", "Vendor name.")], branch=invoice)     # object form
    .extract([Schema.string("merchant", "Merchant name.")], branch="RECEIPT")  # id form
    .create()
)

result = workflow.run("doc.pdf").wait()
for category in result.raw["classifications"]:
    print(category["category"], category["confidence"])

5. Split a multi-document file, then extract

split() carves one upload into sub-documents by rule. Extracts branch off the splitter rule id.

statements = SplitterRule(id="STMT", name="Statement", description="A bank statement.")
checks = SplitterRule(id="CHECK", name="Check", description="A scanned check.")

workflow = (
    client.workflow("Statement bundle")
    .parse()
    .split(statements, checks)
    .extract([Schema.string("account", "Account number.")], branch="STMT")
    .extract([Schema.float("amount", "Check amount.")], branch="CHECK")
    .create()
)

result = workflow.run("bundle.pdf").wait()
for split in result.raw["splits"]:
    print(split["name"], split["files"])

When you chain split() after classify(), tell the splitter which category feeds it with route_from=:

(
    client.workflow("Classify then split")
    .parse()
    .classify(invoice, receipt)
    .split(statements, checks, route_from=invoice)
    .extract([Schema.string("account", "Account number.")], branch="STMT")
    .create()
)

6. Validate extracted fields

validate() adds rules the model checks against the extracted values. It must follow an extract(); use branch= to target a specific extract in a multi-branch workflow.

workflow = (
    client.workflow("Invoice with checks")
    .parse()
    .extract([
        Schema.float("subtotal", "Subtotal before tax."),
        Schema.float("tax", "Tax amount."),
        Schema.float("total", "Grand total."),
    ])
    .validate(
        ValidationRule(id="totals", description="total must equal subtotal + tax.", severity="error"),
        ValidationRule(id="positive", description="All amounts must be positive.", severity="warning"),
    )
    .create()
)

7. Different file inputs

run() accepts a path string, a Path, or raw bytes.

from pathlib import Path

workflow.run("invoice.pdf")                       # path string
workflow.run(Path("scans") / "page.png")          # Path (PDF or image)
workflow.run(open("invoice.pdf", "rb").read())    # raw bytes

8. Async client

AsyncClient mirrors the sync API with await on create, run, and wait.

import asyncio
from anyformat.sdk import AsyncClient
from anyformat.workflow import Schema

async def main():
    client = AsyncClient(api_key="af_...")
    workflow = await (
        client.workflow("Async invoices")
        .parse()
        .extract([Schema.string("vendor", "Vendor name.")])
        .create()
    )
    run = await workflow.run("invoice.pdf")
    result = await run.wait()
    print(result.fields["vendor"].value)
    await client.aclose()

asyncio.run(main())

CLI

The package installs an afx command for one-off parses without writing code:

export ANYFORMAT_API_KEY=af_...
afx parse invoice.pdf

API quick reference

Call Returns Notes
Client(api_key, base_url=...) Client ANYFORMAT_API_KEY if api_key omitted
client.workflow(name) builder chain verbs, then .create()
.parse(mode="standard"|"agentic") builder exactly one per workflow
.classify(*categories) builder branching; needs parse() first
.split(*rules, route_from=...) builder route_from required after classify()
.extract(fields, branch=...) builder branch required after classify/split
.validate(*rules, branch=...) builder follows extract()
.create() Workflow registers the workflow
workflow.run(file) Run file: path | Path | bytes
run.wait(timeout=300, poll_interval=3) Result polls until done
result.fields dict[str, ExtractedField] .value, .confidence, .evidence
result.parse ParseView | None .blocks, .markdown, .text, .draw()
result.parse.draw(out_dir, source=None, dpi=150) list[Path] one PNG per page
result.raw dict full results payload

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

anyformat-0.6.1.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

anyformat-0.6.1-py3-none-any.whl (40.5 kB view details)

Uploaded Python 3

File details

Details for the file anyformat-0.6.1.tar.gz.

File metadata

  • Download URL: anyformat-0.6.1.tar.gz
  • Upload date:
  • Size: 31.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for anyformat-0.6.1.tar.gz
Algorithm Hash digest
SHA256 d4fcf5fe4b70dff8a54cefd14eed3e9d1078a3c4e3fc2068c9c8ee3f058541e6
MD5 1b04d63f8bcd7b654d930287c3368f94
BLAKE2b-256 801a6a2bca1b54b165ecc6a877c39f3b8f1338822f0eef9c5879065e9efe8b75

See more details on using hashes here.

File details

Details for the file anyformat-0.6.1-py3-none-any.whl.

File metadata

  • Download URL: anyformat-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 40.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for anyformat-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 435fc2ba8bfb1c80cbabb1e8fd059206f7aecaec1ff21075f44d9bcc00e026fb
MD5 be37dba2cc5d836536919310ae0ea853
BLAKE2b-256 4d030d28dbd94dbeda56f8122eafe691efa4034e9f83103db818f0d6fdc437dd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page