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

# May 2026

> Changes shipped in May 2026.

### May 28, 2026

**\[Changed]** Validator nodes now consume credits

`ValidateNode` previously ran for free. Each rule in a validator now costs 25 credits, billed once per extraction. The bill scales with the number of rules in the validator, not with the number of pages or files in the document packet.

<Accordion title="Technical detail">
  A new `validate` operator (`Credits(25)`) was added to `DEFAULT_OPERATOR_PRICES`. The internal billing dispatch was reshaped from `(item) -> list[OperatorName]` to `(item, page_count) -> list[LineItem]`, so non-page-priced operators like validators can ignore the page count and substitute `len(rules)` as the billed unit. The processor now issues a single `SpendCreditV4` call per extraction with all line items combined.
</Accordion>

**\[Changed]** Default operator prices realigned with the public pricing page

Default credit prices for several operators now match [anyformat.ai/pricing](https://anyformat.ai/pricing): Parse 25 (was 30), Extract 35 (was 20), Splitter 25 (was 10), Parse (Agentic) 100 (was 75), Extract (Agentic) 150 (was 75). Classify is now billed at 10 credits/page (previously a no-op — running a Classify node was free regardless of the published rate). Organizations with enterprise-specific overrides on `OperatorPriceTable` are unaffected.

**\[Improved]** Documentation restructured into Concepts / Guides / API Reference

[docs.anyformat.ai](https://docs.anyformat.ai) has been reorganized into three scoped tabs. **Concepts** holds definitional pages (Workflows, Schemas, Fields, etc.). **Guides** holds task-oriented walkthroughs (Quickstart, Build workflows, Recipes). **API Reference** holds the REST spec only. The four overlapping "create → run → results" walkthroughs are merged into one Quickstart with UI / curl / Python tabs at each step, and every extraction recipe now uses the canonical typed-graph workflow shape (`{name, nodes, edges}`) instead of the legacy `{fields:[...]}` shortcut. Old URLs redirect.

**\[Improved]** Post-signup `/welcome` page is now reachable only from the signup callback

Loading `/welcome` directly (typed URL, bookmark, fresh tab) now bounces to `/`. The page is only reachable after a brand-new Auth0 signup callback, which closes a path that could let the upstream GA4 / GTM conversion event fire for already-signed-up users.

**\[Improved]** `@anyformat/skill` synced to npm 0.2.2

The `@anyformat/skill` package version in this repo is now aligned with what's live on npm (`0.2.2`). The publish script uses token-based auth and bumps from `max(local, registry)` to prevent version collisions on the next release.

**\[Improved]** Release-PR generation now audits merged PRs upfront

The `/prod-release` Claude Code command (used to open this release PR) now treats the merged-PR list as the ground truth and the changelog as a derived artifact. The audit step surfaced the gap that produced the entries below — previously, PRs merged without a changelog entry could quietly drop out of the release notes.

### May 27, 2026

**\[New]** Attach smart lookup files to fields in the workflow SDK

The Python and JS SDKs can now mark a field as a smart lookup field and attach a reference file when creating a typed workflow. Flag the field with `lookup` and point it at a local CSV or text file; the SDK reads the file and uploads it inline, and extraction resolves that field's values against your reference data. Lookup files are validated as UTF-8 text before anything is written — base64 payloads are never stored or returned.

<Accordion title="Technical detail">
  `POST /api/v2/workflows/` accepts a per-field `lookup` flag (mapped to `source=smart_lookup`) and inline base64 `lookup_files`. Files are pre-validated as UTF-8 text/CSV (with cp1252→UTF-8 transcoding); binary or non-text uploads are rejected with a `400 UNSUPPORTED_LOOKUP_FILE` before any S3 writes. Only S3-backed lookup file references are persisted; base64 payloads and URIs are never stored or echoed back.
</Accordion>

**\[Improved]** Excel workbooks now convert to one Markdown page per sheet

Excel→Markdown conversion now emits a separate page for each worksheet, in workbook order, instead of concatenating every sheet into a single page. Multi-tab spreadsheets now classify and process per tab, keeping each sheet's content distinct downstream.

**\[New]** Official Python SDK now available on PyPI

`pip install anyformat-sdk` — the `anyformat-sdk` package is now live on PyPI. Use the fluent builder to author, create, run, and await workflows end-to-end without writing HTTP code; sync and async clients are included, with typed errors (`BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `RateLimited`, `ServerError`, `SDKTimeout`) and a `.raw` escape hatch on every response. The Claude Code skill at `@anyformat/skill` now documents the Python SDK alongside the JS/TS one.

**\[New]** Post-signup `/welcome` landing page

Brand-new signups now land on a dedicated `/welcome` page (instead of going straight to `/`), giving marketing a stable URL for Google Ads conversion configuration via GA4 / GTM URL match. Returning users continue to land on `/` (or the originally-requested URL) as before.

**\[Fixed]** New signups for an already-registered email address are now rejected

Across every API write site that can create a User row (Auth0 just-in-time provisioning, the E2E test-auth endpoint, the `create_user` management command), case-insensitive duplicate emails are now refused before a second row can be created. Auth0 email rotation that would collide with an existing user is also refused. Previously, mixed-case variants of the same address could silently mint duplicate users.

**\[Fixed]** Resolved four frontend dependency vulnerabilities

`qs` (DoS in stringify), `tmp` (path traversal), `uuid` (missing buffer bounds check), and `js-cookie` (prototype hijack) advisories are now resolved via package overrides in `anyformat/services/frontend`. Upstream parents (cypress, exceljs, segment-analytics) hadn't yet adopted the patched versions, so overrides were the minimally-disruptive path.

**\[Improved]** Authenticated images are no longer refetched multiple times per page load

The sidebar mounts the organization logo from three places, which previously meant three identical requests to `/api/v3/iam/organizations/{id}/logo/` on every page load. The image hook now uses TanStack Query so concurrent subscribers share one in-flight request and the result is cached for 5 minutes.

**\[Improved]** Local development supports both `localhost` and `127.0.0.1` for OAuth

For developers running the stack locally, both `http://localhost:<port>/callback` and `http://127.0.0.1:<port>/callback` are now valid OAuth callback URIs and CORS / CSRF origins. Previously only one was registered per environment, which caused login failures if the dev frontend was opened at the other hostname.

**\[Improved]** JS SDK build toolchain refreshed

`esbuild` (0.21.5 → 0.27.7) and `vitest` (2.1.9 → 4.1.7) in the JS SDK package have been updated. No behavioral changes to the SDK itself.

### May 26, 2026

**\[Improved]** Higher monthly allowance on the Business plan

The Business plan's monthly credit grant has increased to 500,000 credits.

**\[Changed]** Free tier now gets a one-time 50,000-credit signup grant instead of a monthly allowance

Free organizations no longer participate in the monthly credit rotation. Each new free org now receives a single 50,000-credit signup grant that never expires — generous enough to evaluate the product, finite enough to make upgrading the natural next step. Paying tiers (Business, Enterprise) keep their recurring monthly allowance unchanged.

**\[Changed]** Stripe top-up pricing is now tier-aware

Top-up checkout rates depend on the organization's tier: free orgs pay €1.50 per 1,000 credits (€0.0015 / credit), Business and Enterprise orgs pay €1.00 per 1,000 credits (€0.001 / credit). Top-up amounts are now denominated in credits (minimum 30,000) instead of euros.

### May 25, 2026

**\[New]** Visualize parse layout with `result.parse.draw()`

The Python SDK can now render a document's parsed layout as an image overlay. Call `result.parse.draw()` to get the page with detected blocks drawn on top — useful for debugging extraction and verifying bounding boxes. A new cookbook example walks through it.

**\[Changed]** Simplified parse configuration

The `engine` (Fast/Performant), `effort`, and `visual_grounding` parse knobs have been removed from Studio and the workflow SDK. `engine` was a no-op (both options mapped to the same model), and the other two now use sensible built-in defaults. `figure_enhancement` and `mode` remain configurable. Existing workflows are unaffected — stored values for the removed knobs are ignored gracefully.

**\[New]** Install the anyformat Claude Code skill from npm

The anyformat Claude Code skill is now published on npm as `@anyformat/skill`. Run `npx @anyformat/skill` to install it — Claude Code can then author, run, and inspect anyformat workflows directly from your editor using your `ANYFORMAT_API_KEY`. Public docs include a new "Claude Code Skill" section under the SDKs page.

**\[Fixed]** Classify and split workflows authored via the Python SDK now route to extraction correctly

Workflows built with the Python SDK's `classify(...)` / `split(...)` builders no longer silently skip downstream extraction when a category or split rule is attached by name. The typed graph renderer now resolves branch ports by name (matching the schema's edge labels) instead of internal IDs, so SDK-authored classify/split workflows produce the same extractions as Studio-authored ones.

**\[Fixed]** OCR text containing NUL bytes no longer fails extraction

Extractions whose parse output contained a U+0000 (NUL) character inside a text value previously failed at the database write step, marking the entire batch as errored. The worker now strips NUL bytes from text at the parsing boundary so these documents complete normally.

**\[API]** Organization balance endpoint now returns a per-bucket breakdown

The new organization balance endpoint reports credits broken down by their source bucket — monthly grant, top-ups, and each redeemed voucher — along with the organization's tier and the monthly anchor day. Read-only by design: querying the endpoint never triggers a balance reset or recompute.

<Accordion title="Technical detail">
  `GET /api/v3/billing/organizations/{org_id}/balance/` returns `{ tier, monthly_anchor_at, monthly: {...}, topup: {...}, vouchers: [...], overdraft: {...} }`. Pre-migration organizations return zeroed buckets with a stable shape. Membership is enforced; any role can read.
</Accordion>

**\[New]** New organizations receive a monthly credit grant on signup

Newly created organizations now receive a monthly credit grant scoped to their tier, which resets on the organization's anchor day each month. This replaces the previous one-time welcome top-up and makes ongoing free usage predictable instead of front-loaded.

**\[Improved]** Voucher credits now expire after a configurable lifetime

Vouchers can now carry a `credit_lifetime_days` setting (default 90 days). When a voucher is redeemed, the granted credits expire after that many days, so promotional credits don't sit in a wallet indefinitely. Existing vouchers continue to behave as before until the new field is set.

**\[Fixed]** Signup voucher hint shows voucher value only, in your locale

The voucher preview on the create-organization and join-or-create pages no longer double-counts the welcome grant — a 5,000-credit voucher now renders as "5,000 credits" instead of being added on top of the standard signup grant. The number is also formatted using the browser's locale instead of being hardcoded to German formatting.

### May 22, 2026

**\[New]** Official Python SDK and `afx` CLI

The `anyformat-sdk` package wraps the workflow API with sync and async clients so you can author, create, run, and await a workflow end-to-end without writing HTTP code. Typed errors (`BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `RateLimited`, `ServerError`, `SDKTimeout`) make integration code easier to write correctly, and a `.raw` escape hatch on every response exposes the underlying JSON when you need it.

<Accordion title="Technical detail">
  ```python theme={null}
  from pathlib import Path
  from anyformat.sdk import Client
  from anyformat.workflow import Schema

  client = Client(api_key="...")
  workflow = (
      client.workflow("Invoices")
          .parse(mode="agentic")
          .extract([
              Schema.string("vendor", "The company that issued the invoice"),
              Schema.string("total",  "The total amount due, including currency"),
          ])
          .create()
  )
  run = workflow.run(file=Path("invoice.pdf"))
  result = run.wait(timeout=300)

  print(result.fields["vendor"].value)
  ```

  `AsyncClient` mirrors the sync surface for async codebases.
</Accordion>

**\[New]** `afx` CLI for one-shot parse and extract

The SDK ships an `afx` CLI for quick command-line use. Run `afx parse --file invoice.pdf` to get markdown, or `afx extract --file invoice.pdf --field "vendor:vendor company" --field "total:total amount"` to extract structured fields. Pass `--schema fields.json` to load a richer schema, `--json` for machine-readable output, and `-o/--output FILE` to write results to disk.

<Accordion title="Technical detail">
  ```
  export ANYFORMAT_API_KEY=sk_...
  afx parse   --file invoice.pdf
  afx extract --file invoice.pdf --field "vendor:vendor company" --field "total:total amount"
  afx extract --file invoice.pdf --schema fields.json --json -o results.json
  ```

  Spinners, step breadcrumbs, and the Markdown/table renderers all detect TTYs and stay quiet when output is piped or `--json` is set, so the CLI is safe to script.
</Accordion>

**\[New]** Workflow versions are now semver-numbered

Workflow versions now display as `v1.0`, `v1.1`, `v2.0` across Studio, version history, and the save toast. Saving a workflow only mints a new version when it actually changes — major bump on graph topology or node config changes, minor bump on extraction-schema changes, and no bump at all when you only move nodes on the canvas or reorder columns in the data viewer.

**\[API]** Save layout and reorder fields without minting a new workflow version

New workflow-centric write endpoints separate cosmetic edits from material ones. Layout drags and column reorders no longer create new versions or break stable links to a specific version of your workflow. The legacy v2 write endpoints continue to work but now respond with `Deprecation: true`.

<Accordion title="Technical detail">
  * `PATCH /api/v3/workflows/{id}/config/` — save (may bump major or minor)
  * `PATCH /api/v3/workflows/{id}/layout/?version_id=X` — canvas position only (never bumps)
  * `PATCH /api/v3/workflows/{id}/field-order/?version_id=X` — column reorder only (never bumps)

  Concurrent edits surface a `409` with `latest_version_id` in the response body so your client can rebase without losing local edits. The deprecated v2 endpoints (`PUT /api/v2/.../workflows/{id}/config/` and `PATCH /api/v2/.../workflows/{id}/reorder-fields/`) still function during the transition window.
</Accordion>

**\[New]** `npx anyformat-skill` installs the anyformat Claude Code skill

If you use Claude Code, you can now install the anyformat skill with a single command: `npx anyformat-skill` drops it into `~/.claude/skills/anyformat`, or `npx anyformat-skill --project` installs it into the current project. The skill teaches Claude how to drive the typed-graph workflow API end to end — create a workflow with nodes and edges, upload a file, run extraction, and poll for results.

**\[Fixed]** Multi-tab login no longer fails with a code-verifier error

Logging in from two browser tabs at once could clobber the shared PKCE code verifier and surface as a generic "Login failed" page. The callback now silently retries once on this race and lands you on your original destination URL.

### May 21, 2026

**\[Improved]** Cleaner parsed markdown — no structural metadata wrappers

Parsed markdown saved to S3 and returned from the parse-result endpoints no longer carries `<DOCUMENT>` framing or `<section data-bbox=…>` wrappers. Block boundaries are now expressed as inert `<a id></a>` anchors that render as nothing in any markdown viewer, and page boundaries are joined with blank lines. Bounding boxes, page numbers, and confidences remain available via the structured results endpoints — they just stop leaking into the human-readable file body.

**\[New]** Modelo 200 workflow template (Spanish corporate income tax)

A new "Modelo 200" template lands in the workflow gallery, oriented around credit underwriting and portfolio early-warning. It covers balance-sheet and profit-and-loss extraction with 45 root fields (liquidity, leverage, profitability, cash conversion, coverage ratios) plus two table fields for directors and principal shareholders. The template is pre-configured to use agentic parsing with Balanced effort.

**\[Fixed]** Nested table columns no longer dropped by the smart-table extractor

Columns of a list-of-objects field (for example, items inside a product table) could previously be missed because the smart-table planner tried to extract them as standalone scalars. Nested fields are now correctly treated as per-row columns of their parent table and reach the final extraction every time.

**\[Fixed]** Editing object-table cells no longer loses focus mid-typing

Validating a value in an object-field table sometimes stole keyboard focus from the input before you finished typing, occasionally swallowing characters. Cell focus is now preserved across both the validation round-trip and the background refetch, and screen-reader row labels stay in sync after row swaps.

**\[Fixed]** Parse → split workflows now return their splits

Workflows that parse and split a document without a downstream extract were silently dropping the resulting splits. Terminal-splitter workflows now persist and return their splits as expected, surfaced via the file-list endpoint with `?include=splits`.

### May 20, 2026

**\[API]** `POST /v2/workflows/` now takes a typed graph; the old fields-only body is gone

`POST /v2/workflows/` now accepts the typed `nodes` + `edges` graph that previously lived at `POST /v2/workflows/with_graph/`. The legacy fields-only body and the `with_graph` path have been removed — there is one canonical workflow-create endpoint going forward.

<Accordion title="Technical detail">
  The `with_graph/` path no longer exists at `api.anyformat.ai/v2/workflows/`. Send the same payload (`{name, description?, nodes, edges}`) to `POST /v2/workflows/`. The legacy body `{name, fields}` is no longer accepted — wrap your fields in an explicit `parse → extract` graph instead. The legacy `multipart/form-data` upload path (creating a workflow from a downloaded `json_file`) has also been removed; rehydrate the workflow into the typed-graph body client-side.
</Accordion>

**\[Changed]** `POST /v2/workflows/{id}/run/` response `status` is now `"pending"` instead of `"success"`

The public run endpoint accepts requests asynchronously (`202 Accepted` — the extraction is enqueued, not complete). The previous `"success"` value conflated acceptance with completion; the new `"pending"` matches the vocabulary used by `WorkflowRunListItem.status` elsewhere on the v2 surface. Clients that polled for results regardless of this field are unaffected. Clients that branched on `"success"` to skip polling should now poll on `"pending"`; semantics match what was always intended.

**\[New]** Per-branch Validation tabs across workflow and file detail pages

The Validate node now gets the same per-branch UX as Extract. The workflow page renders one Validation tab per branch alongside its existing Category and Extraction tabs, and the file detail page mirrors the same layout. Split workflows include a subdocument selector with chevrons and a `1/N` counter, so you can step through per-subdocument validation results without leaving the tab.

**\[Improved]** Clear "Not validated" placeholders for rules added after extraction

When you add or edit a validation rule, files that were processed before the change now render explicit "Not validated" cards and cells with a hover-card explanation, instead of an ambiguous "N/A". This makes it obvious which results reflect the current rule set and which would need a re-run.

**\[Improved]** Redesigned Validate node configuration menu

The Validate node menu now uses a tabbed Config / Rules layout aligned with the Classify menu, with a dedicated rule editor that supports inline field chips inside rule descriptions. The previous template-import button has been removed in favor of the new layout.

**\[Fixed]** Validation verdicts now populate for split workflows

In workflows with a Split node, validation verdicts could come back empty in the file list and detail view because the API only inspected the parent extraction. Validation results now traverse parent and child extractions so per-subdocument verdicts surface correctly across the workflow page Validation tab, the file detail page, and the file results UI.

**\[Fixed]** CSV and master-file uploads decode reliably from non-UTF-8 encodings

Uploaded CSV files and master files saved in cp1252 (Word/Outlook smart quotes, Spanish accents), Shift-JIS, GB18030, KOI8, and similar non-UTF-8 encodings could previously either silently produce mojibake or fail extraction with a Unicode decode error. Uploads are now transcoded to UTF-8 at the API boundary, with binary payloads disguised as text rejected up front, so downstream parsing and lookups receive consistent, correctly-decoded bytes.

<Accordion title="Technical detail">
  The master-file upload endpoint and workflow CSV upload path now run inputs through a strict Western decoding ladder (`utf-8-sig` → `utf-8` → `cp1252`) before falling back to charset detection, and persist the re-encoded bytes to storage with a truthful `Content-Type: …; charset=utf-8` header. Allowed text extensions are `csv`, `txt`, `md`, `markdown`, and `rst`. Non-text payloads (PDF, ZIP, images, executables) are rejected at upload time rather than being mis-decoded.
</Accordion>

### May 19, 2026

**\[API]** Fetch parsed blocks with bounding boxes via the new parse-result endpoint

The new parse-result endpoint returns the structured page-nested blocks produced by parsing — including bounding boxes, type, confidence, reading order, and layout — along with a presigned URL for the raw markdown. Studio's PDF viewer and JSON view now use these blocks to render overlays consistently across tabs.

<Accordion title="Technical detail">
  `GET /api/v3/files/{file_id}/parse-result/` returns `{ blocks: [...], markdown_url: "..." }`. Blocks are grouped per page and carry a flat `bbox: { x0, y0, x1, y1 }`. The endpoint accepts an optional `execution_id` to scope results to a specific workflow execution.
</Accordion>

**\[API]** Slim split results without nested rows

You can now request slim per-split results that omit nested object rows entirely, and then load nested rows lazily per split via a dedicated endpoint. Each slim split carries a per-split version token so caches can invalidate at the split granularity instead of the whole file.

<Accordion title="Technical detail">
  Pass `?include=splits_lite` on the file results endpoint to receive slim per-split entries (mutually exclusive with `?include=splits`). Use `GET /workflow-versions/{version}/files/{file_id}/splits/{split_id}/object-results/{field_persistent_id}/` to fetch paginated object rows scoped to a single split.
</Accordion>

**\[Improved]** Faster Studio category tab on workflows with many splits

The Studio category tab now requests slim payloads and fetches nested object rows lazily per split, so opening a file with many splits no longer waits on full results materialization up front. Bounded prefetching when hovering across many cells also keeps the page responsive.

**\[Improved]** Parse JSON view shows the full block on expand

Expanding a block in the Parse → JSON tab now reveals the complete block object — id, bbox, confidence, content, hyperlinks, rows, and `image_base64` — rendered as inert JSON. Pages are grouped under collapsible sticky headers (collapsed by default) for easier navigation in multi-page documents, and the displayed shape mirrors the public parse-result response exactly.

**\[Improved]** Client-side markdown hydration with embedded images

The Markdown tab now hydrates images directly in the browser using each block's bounding box — including `figure` blocks — so the in-tab view and Markdown downloads stay consistent. The dedicated server-side hydration pipeline has been retired in favor of this lighter approach.

**\[New]** "Beta" badge on Studio nodes still being stabilized

Studio nodes can now display a BETA badge in the sidebar, on the canvas header, and in the sidebar hover card. The **Validate** node is the first to use it, marking it as still being stabilized for general production use.

**\[Fixed]** Workflows created via the typed graph endpoint now run extraction

Workflows created via the typed `with_graph` create endpoint sometimes failed to run extraction and surfaced as `EXTRACTION_FAILED (422)` because the extract node was persisted without an engine. New workflows now always include an engine and execute end-to-end as expected.

<Accordion title="Technical detail">
  `POST /v2/workflows/with_graph/` now persists a default engine on every extract node it creates.
</Accordion>

**\[Fixed]** Object rows in sibling extract branches no longer collide

When a workflow had sibling extract branches that defined fields with overlapping names, object rows from one branch could be written against the wrong field. Field schema resolution is now scoped per extract node, so each branch's rows land where they belong.

**\[Fixed]** Parse results reliably appear in the UI for new workflow versions

Some parsed files showed missing pages or stale `parsed_at` because parse callbacks resolved graph nodes by an identifier that wasn't unique across workflow versions. Callbacks now scope to the active workflow version, so parse results are persisted to the correct file every time.

**\[Improved]** Other improvements and fixes

Deleted object rows are now consistently excluded from both per-split and file-level row counts; the file results UI deduplicates parse-result requests when navigating between files; and extraction progress estimates fall back to organization-scoped percentiles instead of cross-tenant numbers.

### May 18, 2026

**\[New]** Handwritten text is now a first-class block type in agentic parsing

Agentic PDF-to-Markdown now recognizes handwritten text as its own block type, with a dedicated extraction strategy that combines a vision-language transcription with OCR-derived hints. Handwritten blocks are labeled "Handwritten text" in the Studio UI, distinct from machine-printed text, and can be tuned independently from regular text extraction.

**\[Improved]** More consistent agentic parsing when text-class blocks have multiple sources

When a text-class block has multiple candidate text sources, the agentic parser now writes the chosen source back into the block's elements and regenerates its segments before rendering. Downstream grounding (overlays, citations) now matches the markdown that's actually rendered.

### May 14, 2026

**\[New]** "Beta" badge on agentic parsing mode

The Parse node mode card now shows a "Beta" badge next to **Agentic parsing**, making it clear that this parse mode is still being stabilized for general production use.

**\[New]** Calibrated confidence scores on smart-table extractions

Both table cells and scalar fields produced by the smart-table extractor now carry a calibrated 0–100 confidence, so review UIs and downstream consumers can prioritize low-confidence values. The scores degrade gracefully when scoring is unavailable, so extractions still complete normally.

<Accordion title="Technical detail">
  Confidence is surfaced on each extracted value's `metadata.confidence` field as an integer in `[0, 100]`. Both judges flip together via the `smart_table_confidence_strategy` setting (`auto` / `judge` / `none`); the default is on.
</Accordion>

**\[New]** Review threshold filter for file results

File results now include a confidence-threshold slider that filters Fields and JSON to show only extractions at or below the selected confidence — useful for prioritizing low-confidence rows that need human review. The threshold is persisted across reloads and tabs, and an inline hover card explains how the filter works.

**\[Improved]** Image previews use the PDF viewer toolbar

Image uploads (JPEG, PNG, GIF, BMP, TIFF) now render through the same viewer as PDFs and inherit its zoom, rotate, and full-page controls. Multi-page TIFFs gain a page selector, and the viewer now reports specific errors for unsupported formats or images that exceed browser size and canvas limits instead of failing silently.

**\[Improved]** More reliable dense-table parsing in agentic mode

Agentic PDF-to-Markdown table parsing no longer stalls on dense tables. Reasoning-token budgets are now properly honored across the heal, audit, and sub-region passes; partially valid heal plans are applied operation-by-operation instead of being discarded on a single bad op; and the table-parsing token cap has been raised so large tables are no longer truncated mid-document. Workflows that previously stalled for over 15 minutes now finish in roughly 3.

**\[Improved]** Organization logos stay cached when unrelated settings change

The organization logo URL is now invalidated only when the logo bytes themselves change, not whenever any field on the organization is updated. Renaming the org, toggling support access, or any other non-logo edit no longer forces every client to re-download the logo.

**\[Fixed]** Bulk-download of selected files no longer 400s

`GET /api/v2/saas_manager/workflow-versions/{id}/results/` rejected the `file__id__in` and `file__id__not_in` query parameters with `Field 'id' expected a number but got '...'` whenever the frontend passed UUIDs (the v2 file API now returns file `id` as a UUID string). The filter was still joining against the legacy integer PK; it now joins on the file UUID field, matching the regular file filter. Every bulk-download flow that relies on row selection — and the "select all minus N" pattern — is unblocked.

**\[Fixed]** Stricter validation of logo and workflow file uploads

Logo and workflow file uploads are now validated against their actual bytes via magic-byte sniffing rather than the client-supplied `Content-Type` header, and only file types the parse pipeline can actually process are accepted. Uploading a renamed executable, ZIP, or arbitrary JSON as a workflow file is now rejected at the API boundary instead of silently failing later in the pipeline. Stored objects also carry the correct `Content-Type` on the storage layer instead of being labeled `binary/octet-stream`.

**\[Fixed]** Switching organizations on a resource page no longer reverts

Switching the active organization from a workflow detail page (or any other resource page deep-linked from a different org) could occasionally snap back to the original org after showing a "switched" toast. The selection is now preserved on the first click.

**\[Fixed]** Object-field row counts exclude deleted rows

The paginated object-field results endpoint and the slim workflow-results list both now exclude soft-deleted rows from page contents and the row-count total. Counts shown in the UI and returned by the API now match the rows you can actually see.

**\[Fixed]** Sub-table detail rows no longer clip at the bottom

Expanded sub-tables inside object-field result tables previously cut off the bottom row because the row-height calculation didn't account for the surrounding padding and border. The detail row now renders at full height regardless of how many sub-rows it contains.

**\[Fixed]** Classifier category tabs render again on workflow results

Category tabs on classifier workflow results were rendering empty because the slim results response had stopped including the classifier's `graph_node_id`. The field is back in the response (with a batched prefetch to avoid per-file queries), so files routed by a classifier now appear under their correct tab.

**\[Fixed]** Workflow list updates immediately after create, duplicate, or delete

The home page workflow list previously kept the stale list cached after creating, duplicating, or deleting a workflow, so the change only appeared after a refresh. All three mutations now invalidate the workflow list cache and the list reflects the change right away.

### May 13, 2026

<Warning>
  **\[Breaking + API]** Workflow creation no longer accepts file uploads

  `POST /api/v2/saas_manager/organizations/{org_id}/workflows/` previously accepted a `multipart/form-data` body to import a workflow from a JSON or XLSX template file. That entry point and its template downloads have been removed; the endpoint now accepts JSON request bodies only.

  **Action required:** if you were uploading a JSON or XLSX template to this endpoint, switch to sending the equivalent JSON body directly. The chat-driven and from-scratch flows in Studio are unaffected.
</Warning>

**\[New]** Studio is now generally available

The visual workflow editor (Studio) is now visible to every organization member from the workflow page and the new-workflow page. Write access is still controlled by your workflow role. The legacy schema editor at `/workflows/edit/...` has been removed — the Studio tab is now the single editing surface.

**\[New]** Row-level actions on object-field result tables

Object-field result tables now expose a per-row dropdown with **Validate row**, **Add row above**, **Add row below**, and **Remove row**, plus inline up/down chevrons for adjacent reordering. The trailing-column header has a new **Validate all rows** action that promotes every datapoint in the field to human-verified in a single click.

<Accordion title="Technical detail">
  New endpoints under `/api/v2/datapoints/object-fields/{field_id}/`:

  * `POST .../rows/` (with an `index` in `[0, n_live_rows]`) inserts an empty row and shifts live siblings up by one in the same transaction.
  * `POST .../rows/{a}/swap/{b}/` exchanges two adjacent live rows.
  * `POST .../rows/{position}/validate/` bulk-promotes every datapoint in a row to human-verified.
  * `POST .../validate-all/` does the same for every live row of the object field.

  All four endpoints require Member role.
</Accordion>

**\[New]** Signup vouchers now give live feedback during onboarding

When a user lands on the signup flow with a voucher code, the create-organization screen shows the total welcome credits they will receive (the standard signup grant plus the voucher's value, fetched live from the backend) if the voucher is valid, or an amber "voucher no longer valid" hint if it is expired or unknown. Previously the page was silent in both cases.

<Accordion title="Technical detail">
  `GET /api/v3/iam/signup-vouchers/{code}/` returns a read-only preview of the voucher (`{ code, additional_credits }`) and never consumes a redemption — actual redemption still happens atomically inside organization creation. Returns `404 voucher_not_found` for unknown codes and `410 voucher_expired` for expired or exhausted ones. Responses set `Cache-Control: no-store, private` so admin edits to a voucher are reflected on the next page load.
</Accordion>

**\[Improved]** Workflow page list is dramatically faster

The workflow page's file list now uses a slim response that omits per-cell datapoint detail until you actually expand an object row. On a workload of 50 files with 2,000 line-items each, the list now returns in \~240 ms instead of \~6 s and the response payload is roughly 770× smaller. Object cells pre-warm on hover so expanding feels instant; edits to a datapoint refresh the expanded sub-table without a poll cycle.

<Accordion title="Technical detail">
  `GET /api/v2/saas_manager/workflow-versions/{id}/files/?include=results_lite` returns scalar datapoints as `{ id, display, status }` and object fields as `{ row_count }`. Full sub-tables are fetched lazily through `GET /api/v2/saas_manager/workflow-versions/{id}/files/{file_id}/fields/{field_persistent_id}/object-results/`. The existing `include=results` (full) path is unchanged and still available for legacy integrators.
</Accordion>

**\[Improved]** Stripe top-up is now open to all users and confirms the purchased amount

The **Top up** button on the Usage page now opens the Stripe checkout dialog for every user — the previous fallback to a `mailto:sales@anyformat.ai` link is gone. After a successful payment, the success page now states how many credits were added and tells the user they can start running workflows immediately.

**\[Improved]** Cleaner extraction on scanned PDFs

The agentic PDF-to-markdown parser now honors the routing model's verdict on which text source to trust (the embedded PDF text layer, OCR, or vision) for every block type, including miscellaneous blocks that previously ignored the verdict. On scanned PDFs where the embedded text layer is corrupted, blocks now render the clean OCR text instead of garbled bytes.

**\[Fixed]** Returning from Stripe checkout no longer fails authentication

If you stayed on Stripe long enough for the short-lived access token to expire, returning to anyformat used to land on an authorization error screen — even though the payment had already succeeded on Stripe's side. The app now silently refreshes the token in the background (and replays the first request that hits a 401), so the top-up success page renders cleanly even after a long checkout.

**\[Fixed]** Cross-tab refresh storm and support-elevation isolation

Opening the app in multiple tabs no longer triggers refetch storms when you navigate inside one tab. As a separate fix, support agents who elevate into a customer organization stay elevated only in the current tab — new tabs default back to their own organization until they explicitly re-elevate.

**\[Fixed]** New users are immediately enabled after signup

Users who created their own organization or requested to join an existing one were previously left in a disabled state until an admin reviewed their request, which blocked the create-org path entirely. Both onboarding paths now enable the profile immediately. Join-request approval is still required to gain membership in the target org.

**\[Improved]** Other improvements and fixes

* Structured-output extractions on Amazon Nova models no longer occasionally fail with an empty-JSON parsing error.
* The Usage page no longer renders the "Page consumption" summary box at the top — the cleaner credit-balance card now stands on its own.
* Long organization lists on the join-or-create page no longer get clipped below the fold.

### May 12, 2026

<Warning>
  **\[Breaking + API]** v2 file responses now use UUID hex for file IDs

  Every v2 endpoint that previously returned a numeric file ID now emits a 32-character UUID hex string. The OpenAPI schema already declared these fields as UUID strings, so generated SDK clients are unaffected — but raw HTTP integrations that parsed the value as an integer, or that constructed URLs from cached numeric IDs, will need to update. Old integer-shaped URLs return `404`.

  **Action required:** treat v2 `file_id` values as opaque strings, and refresh any cached file URLs that embedded integer IDs.

  <Accordion title="Affected surfaces">
    * File detail, upload responses, and file-position (`prev_id` / `next_id`)
    * Extraction responses and the verification URL builder
    * File filters: `id__in`, `id__not_in`, `file_id__in`, `file_id__not_in` now resolve against UUID hex
  </Accordion>
</Warning>

**\[New]** Delete rows from object-field result tables

Object-field result tables now have a per-row trash action. Remaining rows are reindexed in place so curated positions stay stable and your verification carries over correctly when the file is re-extracted. Rows the model produced are soft-deleted (so accuracy metrics still reflect the model's miss); rows you added yourself are removed entirely.

<Accordion title="Technical detail">
  `DELETE /api/v2/datapoints/object-fields/{id}/rows/{position}/`. Deletion is transactional. A partial unique constraint allows deleted rows to retain their original index, and the result-building SQL filters soft-deleted rows.
</Accordion>

**\[API]** Workflow responses now include `organization_id`

Workflow and workflow-version responses (including the nested `versions` collection on workflow detail) now expose a normalized 32-character hex `organization_id`. Useful when a client needs to identify which org owns a workflow without an extra round-trip.

**\[Improved]** Tax ID and billing address now collected during Stripe Checkout

Top-up Checkout sessions now collect the customer's tax ID and billing address before invoices are finalized. This unblocks invoice correctness in EU/UK jurisdictions. No action required for existing customers — the values are persisted on the next Checkout.

**\[Improved]** Smoother markdown viewer on long documents

The "visual" markdown view is significantly snappier on long documents. Scroll-driven page detection is now frame-throttled and only fires on real page transitions, sections subscribe to fewer state changes, and highlight-scroll listeners share a single event bus instead of one per section.

**\[Improved]** Faster agentic PDF-to-markdown parsing

Agentic PDF→Markdown parsing now parallelizes work per page — single-call vision blocks fan out and agentic blocks run in a small per-page pool, so page latency tracks the slowest block instead of the serial sum. Layout-routing calls were also tuned for lower latency.

**\[Fixed]** Org switcher no longer goes blank for orgs without a logo

The organization switcher trigger now always renders the org's initials as the base layer and only overlays the logo image once it loads successfully — so switching to an org without a logo no longer flashes blank.

**\[Improved]** Parse confidence is now reported even when the model omits logprobs

Per-block parse confidence is now computed and stamped on the rendered markdown regardless of whether the parsing model returns token-level log probabilities. Downstream consumers can read `data-parse-confidence` consistently across all supported parsing providers.

### May 11, 2026

<Warning>
  **\[Breaking + API]** v2 SaaS Manager list, create, and member endpoints now require an organization in the URL

  The bare `/api/v2/saas_manager/...` list and create paths, and the legacy `current` organization shortcuts, have been removed. Workflow versions, file collections, member management, and suggestion routes must now include the org id in the URL. Suggestion endpoints additionally require Member role — Viewers get `403`.

  **Action required:** template the org id into the URL for every v2 SaaS Manager request that previously relied on a bare list/create path or the `current` shortcut.

  <Accordion title="Affected paths">
    * `GET / POST /api/v2/saas_manager/workflow-versions/` → `.../organizations/{org_id}/workflow-versions/`
    * `GET / POST /api/v2/saas_manager/file-collections/` → `.../organizations/{org_id}/file-collections/`
    * `PATCH /api/v2/saas_manager/organizations/current/` → `.../organizations/{id}/`
    * `PATCH /api/v2/saas_manager/organizations/current/logo/` → `.../organizations/{id}/logo/`
    * `GET / POST /api/v2/saas_manager/organizations/current/members/` → `.../organizations/{org_id}/members/`
    * `DELETE / PATCH /api/v2/saas_manager/organizations/current/members/{user_id}/[role/]` → `.../organizations/{org_id}/members/{user_id}/[role/]`
    * `POST /api/v2/suggestions/{fields,field-description,workflow-description,workflow-name,upload-sample}/` → `.../organizations/{org_id}/suggestions/{...}/`
  </Accordion>
</Warning>

**\[New]** Self-service invoice downloads from the usage page

Owners and admins can now open Stripe's hosted Customer Portal from the Usage page to download invoices for past top-ups. Members, reviewers, and viewers don't see the button — invoices are financial documents.

<Accordion title="Technical detail">
  `POST /api/v3/billing/organizations/{org_id}/portal-sessions/` mints a short-lived portal URL bound to the org's billing customer record. Orgs that have never run a top-up return `409 stripe_customer_not_provisioned`; the UI surfaces this as a "Top up first" toast. New environment variable `STRIPE_BILLING_PORTAL_RETURN_URL` falls through to `${FRONTEND_URL}/usage`. Configure the operator side (Invoice History toggle, ToS / privacy URLs, default return link) in the Stripe dashboard.
</Accordion>

**\[Improved]** Cross-tenant probes on v3 detail endpoints now return 404

v3 file results, file-collection extraction status, file-collection results, and webhook delete now derive the active organization from the resource itself. URLs are unchanged. Cross-organization requests get a `404` to mask resource existence; same-org callers are unaffected.

**\[Improved]** Hovering a stacked bounding box highlights every overlapping box

When several bounding boxes overlap, hovering reveals each one stacked under the cursor instead of just the topmost. Rapid mouse movement is collapsed to one diff per frame so the UI stays responsive.

**\[Improved]** Consistent extracted-object UI across Define, Refine, and File Results

Object and table fields now render the same way across the schema builder, refine step, and file-results panel — same headers, same skeletons, same status states. Refine correctly shows error / cancelled copy on failed extractions instead of an empty data view.

### May 9, 2026

<Warning>
  **\[Breaking + API]** Detail and per-resource v2 endpoints no longer read `X-Current-Org`

  Datapoints, manual datapoints, extraction results, workflow-version files, file collections, workflow versions, and workflow detail endpoints now derive the active organization from the resource itself. Non-resource list/create endpoints (API key CRUD, manual-datapoint create, workflow list/create) moved under `/api/v2/saas_manager/organizations/<org_id>/<resource>/`.

  **Action required:** stop sending `X-Current-Org` to v2 endpoints — derive the org from the resource (URLs unchanged for detail) or from the new org-scoped URL for list/create. Non-members on a same-resource probe now get `404`; in-org callers with insufficient role get `403`.
</Warning>

### May 8, 2026

**\[API]** Create workflows with full graph definition in a single call

You can now define a workflow's parse, classify, split, and extract steps in one request — the workflow, its initial version, and all extraction fields are created atomically. The existing workflow-create endpoint is also fully typed, so SDKs get auto-complete and field-level validation.

<Accordion title="Technical detail">
  `POST /v2/workflows/with_graph/` accepts the complete graph plus per-extract-node fields and creates everything in a single transaction. The original `POST /v2/workflows/` body is now declared with discriminated field types.
</Accordion>

**\[New]** Python builder for typed workflows

The Python SDK now ships a fluent `Workflow` builder: `.parse().classify(...).extract(...).split(...).build()` produces a fully validated request. `branch=` and `route_from=` accept either an id string or the typed category/rule you registered earlier, so typos fail at the call site instead of at build time.

**\[Improved]** Workflow graph validation catches more errors before run

Non-branching nodes with multiple outgoing edges, missing `route_from` on classify→split chains, and broken graph transitions are now rejected with clear validation errors instead of producing confusing runtime failures. The new typed graph payload also rolls back cleanly when persistence of one node fails partway.

**\[New]** Voucher codes on signup grant extra wallet credit

Organization creation now accepts an optional `voucher_code` that grants extra wallet credit to the new org. Vouchers (expiry, usage cap, amount) are managed in the admin panel; invalid or expired codes surface as a clear `400` instead of breaking signup.

**\[Improved]** Doc-viewer table polish — resize, unified sync toggle, safer persisted state

The object-results table is now vertically resizable with sensible bounds (header + one row min, all-rows-visible max). The PDF toolbar's separate auto-focus and auto-scroll preferences collapse into a single "Sync document and results" toggle, and the lookup-files dialog no longer overflows. Co-mounted views in the same tab stay in sync, and corrupt persisted preferences self-recover instead of breaking the UI on load.

### May 7, 2026

**\[Improved]** Org isolation on v2 detail endpoints is now resource-derived

Detail endpoints (datapoints, manual datapoints, extractions, workflow-version files, file collections, workflow versions, workflows) now authorize the caller's role against the resource's organization instead of a request header. Cross-tenant probes return `404`; in-tenant callers with insufficient role return `403`. URLs are unchanged.

**\[Improved]** More accurate extraction confidence via LLM judge

For extractions where the model doesn't return logprobs (or where you prefer the judge), a separate judge model now scores each extracted value against its source markdown and outputs a calibrated 0–100 confidence. Object and table cells are batched for cost efficiency. Choose the strategy per task with `extraction_confidence_strategy = none | auto | judge`.

**\[Fixed]** Login no longer spins on token-exchange failures

The post-login callback now exits cleanly when token exchange or profile fetch fails: a 15-second watchdog renders an error page with a logout option, OAuth errors surface explicitly, and tokens are purged on error.

**\[New]** Bounding boxes for split + extract workflows

The PDF viewer now renders bounding boxes for split + extract workflows by scoping mapped locations per panel — the matching split when navigating by partition, the file's extraction otherwise. Parse, split, classify, and validation panels paint no boxes, as intended.

**\[Fixed]** Renamed split categories no longer lose historical sub-documents

When you rename a split rule across versions (e.g. "Invoice" → "Bill"), historical sub-documents continue to surface under the renamed tab — the UI now filters by the stable split-node identifier instead of the display string.

### May 6, 2026

**\[New]** Top up credits with Stripe Checkout

You can now purchase wallet credit through Stripe Checkout from the Usage page. Sessions are idempotent (no duplicate charges on retry), and every successful top-up generates an invoice you can download later from the Customer Portal.

<Accordion title="Technical detail">
  `POST /api/v3/billing/organizations/{id}/checkout-sessions/` validates the requested credit count against per-session min/max bounds and returns a hosted-page URL. Inbound `checkout.session.completed` webhooks are HMAC-verified and serialized to guarantee exactly-once credit grant under contention.
</Accordion>

**\[New]** File results panel with explicit processing states

The per-file extraction panel now renders explicit Unprocessed, Processing, Error, and Processed states with run and retry actions. Heavy result queries only run once the file is actually processed, removing wasted requests and inconsistent loaders for unprocessed files.

**\[New]** Search inside the PDF viewer

The PDF viewer now has a toolbar search bar with case-sensitive/insensitive matching, prev/next navigation that wraps, a `Cmd/Ctrl-F` shortcut, and cross-page highlighting. Search stays responsive on large PDFs thanks to debounced lazy text extraction.

**\[API]** Workflow results now expose classifications, splits, and flat extractions

`GET /v2/workflows/{wid}/files/{cid}/results/` now returns `classifications`, detailed `splits` (with per-file pages and partitions), and a flat `extractions` list keyed by `(split_name, partition)` alongside the existing fields. The v3 file-collection results payload exposes the same keys in place — no new endpoint required.

**\[Improved]** Org switcher orders by ownership, membership, then support access

Organizations are now returned in three alphabetically-sorted buckets — owned, member, support access — replacing the prior database-discovery order. Helpful for accounts that belong to many orgs.

**\[Fixed]** Bulk extraction status no longer marks processed files as pending

Bulk status resolution now aligns identifier formats and queries root extractions only, so processed parents are correctly reported instead of stuck on "pending".

**\[Improved]** Other improvements and fixes

File status badge in the file-results header, PDF toolbar staying visible on multi-line wrap, clearer toasts when a dropped file's type isn't accepted, and a fix for crashes on empty drag-and-drop selections.

### May 5, 2026

**\[Improved]** Smart Lookup retries based on residuals

Smart Lookup now wraps the worker in an orchestrator + auditor loop that can iterate up to three times if residuals or tool-call signals suggest more work is possible. Search blends BM25 and Jaro-Winkler matching with a per-call query cap to improve recall while keeping runtime bounded.

**\[Improved]** Splits panel is now navigable

Clicking a split or partition now switches the extraction tab, persists the selected partition in a URL search param, and scrolls the PDF viewer to the matching page. Refresh-safe deep links to a specific partition just work.

**\[Fixed]** Duplicating workflows with reused field names no longer fails

Workflows that contain multiple extract nodes reusing the same top-level or nested field names can now be duplicated cleanly — field lookups are keyed per source node.

**\[Fixed]** Classify-only workflows handle unwired LLM categories

When the classifier returns a category with no downstream edge, the run now falls through to the end instead of crashing. Classify-only workflows also persist classification records so results are available afterwards.

**\[Improved]** Smart-table list-of-object rows are page-ordered

Smart-table list-of-object extraction rows are now sorted by source page and block, so output is stable and easy to align with the source document across runs. Rows with missing page info are placed last.

**\[Improved]** Cleaner empty states for splits and category grids

Workflows with no rows now render a clean overlay in the splits and category grids instead of stale placeholders.

### May 4, 2026

<Warning>
  **\[Breaking]** Audio uploads are no longer accepted

  The dropzone no longer accepts audio files (`.mp3`, `.wav`, etc.). Existing audio files already in your collections remain viewable and downloadable.

  **Action required:** if your integration uploaded audio for transcription, switch to a supported format. No deprecation window — audio uploads are rejected starting today.
</Warning>

**\[Fixed]** Authentication errors now return the correct status codes

Unauthenticated requests now return `401` (with a `WWW-Authenticate` header) instead of being coerced to `403`. Missing email-claim or unknown-key authentication paths return `AuthenticationFailed` cleanly instead of `500` errors.

**\[Improved]** Profile-fetch failures show a clear error page

Repeated `/profile` failures after login now surface an error page with a logout option instead of stranding users on a spinner. Global query errors are also reported with the failing endpoint for faster triage.

**\[Fixed]** Corrupted local preferences no longer break the UI on load

Persisted UI preferences (sync toggles, panel sizes, etc.) are now safely parsed; corrupt keys are dropped on initial load and reported to monitoring with the offending storage key.
