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.
  • 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.0.tar.gz (30.4 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.0-py3-none-any.whl (38.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: anyformat-0.6.0.tar.gz
  • Upload date:
  • Size: 30.4 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.0.tar.gz
Algorithm Hash digest
SHA256 bf955b9a677fb2433adaa34ee27b99007b1fe0d2bb3d0a76fc33b6b0ba0b4add
MD5 18f165f80c59953a9c524d2d01a187be
BLAKE2b-256 f9c8793c73db78745689175484e3362e7fe2f0e56a67e1045c4b74d1e2faba29

See more details on using hashes here.

File details

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

File metadata

  • Download URL: anyformat-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 38.9 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1de0b8bb0b5fe51f3a5900faed31d04bc91624a0004a5958ca737d626746efbe
MD5 0e0b68e91001334a2c3d1616097d63b5
BLAKE2b-256 3250bcfaf958cc6e91f1a42f72e171da889d96ee6c88e1a4918ab863eff1e22e

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