June 29, 2026
[New + API] Deterministic validation rules — instant, free checks alongside AI rules A Validate step can now mix two kinds of rules. AI rules stay as they were: you describe the condition in plain language and a model judges it. Deterministic rules are new — structured checks that run in plain code with no model call, so they’re instant and free. Seven check types ship: number range, date range (with a relativetoday bound for not-expired checks), arithmetic (subtotal + tax = total, with a tolerance), comparison (a field vs a value or another field), one-of (value in an allowed set), pattern (regex), and required (present and non-empty). Build them in Studio with the new AI / Deterministic toggle on each rule, or author them via the API and SDKs — deterministic rules reference fields by name and carry a check payload, AI rules carry a description. Pattern checks run on a linear-time regex engine, so a user-supplied pattern can’t hang an extraction. Existing workflows are unchanged: rules default to kind: "ai". See Validation rules.
June 22, 2026
[New] Self-serve Business subscriptions, recurring monthly credits, and a billing incident banner Business is now a self-serve plan. From the dashboard, an organisation owner or admin can click Upgrade to Business to open Stripe Checkout, enter a card, and become a paying Business customer in a single round-trip — no support ticket required. Once active, the organisation receives a fresh 100,000-credit monthly grant on each successful renewal, replacing the previous month’s grant rather than stacking on top of it. Enterprise is unchanged in spirit (still sales-led) but rides the same plumbing: support agrees on a custom monthly grant and price, mints a dedicated Stripe price, and pins both to the org on aBillingPlan row. Top-ups still work on top of the recurring grant for one-off credit additions.
Subscription customers get a Customer Portal link from the Usage page to manage their payment method and cancel the subscription themselves. When Stripe reports a failed renewal, a billing incident banner appears on every authenticated page describing the current state (PastDue during the grace window, Suspended if grace expires, Canceled after cancellation) with a deep-link to the action that resolves it — update the card, reactivate, or restart the subscription. Late-payment recovery is exact: if a card is fixed after the grace window has zeroed the grant, the next successful invoice restores cycle_monthly_grant − consumed_during_unpaid_cycle credits (floored at zero), so a customer never pays for and then loses what they already used.
This rollout is gated behind BILLING_SUBSCRIPTIONS_ENABLED. Existing Business and Enterprise organisations whose tier was set by the legacy one-shot upgrade keep their tier through a fallback path until their first real subscription invoice clears.
June 18, 2026
[New + API]GET /v2/workflows/{workflow_id}/definition/ — round-trippable workflow definition
A new sub-resource returns the workflow’s typed graph in exactly the shape PUT /v2/workflows/{workflow_id}/ accepts. SDK callers can now GET, mutate one field, and PUT the result back without re-authoring the rest of the schema. The PATCH side preserves persistent_id per (node_id, field_name) so an unchanged echo cuts no new version (ChangeKind.NONE modulo edge order), and genuine edits keep persistent_id stable for unchanged-by-id fields — analytics, ground-truth, and monitoring continuity all hold across edits.
[Changed + API] Field description is no longer required to be non-empty
The typed surface (AnyField.description) previously enforced min_length=1, but Path A persistence (the Studio frontend’s save path) never enforced it — so legacy workflows can carry fields with empty descriptions that the new GET-definition endpoint needs to round-trip cleanly. Empty descriptions are now accepted on POST /v2/workflows/, PUT /v2/workflows/{id}/, and the generated SDKs. SDKs and docs still encourage non-empty descriptions — an empty value degrades extraction quality, it isn’t structurally invalid.
[New + API] POST /v2/workflows/{workflow_id}/files/from-url/ — upload by URL instead of multipart
A new route accepts an HTTPS URL and tells anyformat to fetch the bytes server-side, returning the same CreateCollectionResponse shape as the multipart POST /v2/workflows/{workflow_id}/files/ upload. Use it when the source document already lives in object storage you can presign (S3, GCS, R2, …) or at a public HTTPS URL — no need to stream bytes through your own backend. The fetch is bounded by a 10-second timeout and the same 20 MB cap as multipart upload, and runs through the outbound-URL SSRF guard so URLs resolving to non-globally-routable IPs (loopback, RFC1918, link-local, cloud-metadata, IPv4-mapped IPv6) are refused before any connection opens. HTTPS-only at the gateway; non-2xx upstream, DNS failure, or timeout surfaces as 422. Documented under Upload File from URL and in Files.
June 15, 2026
[Fixed + API] SDK consumers can pull per-file results through/v2/workflows/{wf}/results/?file_id=... again
The v3 route GET /api/v3/files/{file_id}/results/ declared its workflow_version_id query parameter as int, but the v2 gateway forwards the 10-character WorkflowVersion.external_id string returned by /api/v2/.../latest_version/. Ninja’s int coercion rejected the string with 422, breaking any SDK call that chained latest_version into the per-file-results lookup. The route now accepts workflow_version_id as a string, resolves the external_id to the internal PK in-route, and returns 404 (not 422) for an unknown id — matching every other v3 endpoint’s “the public external_id is the single public identifier” contract.
June 11, 2026
[New + API]POST /api/v3/extractions/workflow-progress-digest — one round-trip for workflow polling
A new digest endpoint returns per-extraction progress for a caller-supplied set of extraction_uuids AND unfiltered per-status file counts for the surrounding workflow_version_id in a single response. The dashboard now polls this endpoint once every 4 seconds while files are processing, replacing three separate polls (/extractions/progress at 2.5s, and the v2 /files/count/ and /files/ polls at 5s). Polling stops automatically when no extraction is queued or in-progress. Counts come back null for unknown or cross-organisation workflow versions; cross-organisation extraction UUIDs are silently dropped, matching the existing single-extraction-progress semantics.
[New + API] Cell-level grounding on extraction evidence
Table-row and scalar evidence now carries true cell provenance end-to-end: evidence and mapped_locations entries on extraction responses gain optional cell and row keys identifying the exact source cell that fed each value. Evidence without cell-level grounding (non-table datapoints, or table rows the model couldn’t ground precisely) stays byte-identical to before — the new keys are emitted only when present. On the reference invoice that motivated this work, table rows with precise grounding went from 0% to 100% (626/626) with zero wrong-row assignments, and scalars whose value lives in a header table (invoice ids, dates) now resolve to one precise evidence on an LLM-cited page instead of a page-backfilled guess.
[New + API] Test-set membership for files
Files can now be designated as part of a curated, locked test set. A new boolean is_test_file is returned on file responses, and two v3 routes manage membership: POST /api/v3/files/{file_id}/test-set to add and DELETE /api/v3/files/{file_id}/test-set to remove. Files in the test set are frozen from re-verification: every verification write (verify, unverify, edit, discard at the datapoint level; verify-extraction; add/delete/swap/verify-row/verify-all at the datarow level, across both v2 and v3) now returns 409 Conflict with code ground_truth_locked, so an extraction designated as ground truth cannot be mutated out from under a downstream accuracy scorer. Ground-truth values and accuracy reporting land in a follow-up release.
[New] Download lookup files from the Extract node UI
Lookup files (master files attached to an Extract node) were upload-only — once added, you couldn’t get them back. The Manage files dialog in the Extract node now has a download button next to each lookup file, backed by a new presigned-URL endpoint GET /api/v2/saas_manager/workflows/{id}/master_file/download/?uri=<s3-uri>. Org membership and the workflow-scoped S3 key prefix are both enforced; the presign is pinned to the canonical bucket so a crafted cross-bucket URI cannot redirect the signed GET.
[Improved] Table extraction now always uses the refine engine
Table parsing previously branched upfront on a heuristic between a cheap simple_table call (one shot, no retry — silent data loss on empty/truncated output) and a much slower dense_table agent (~10–20× the cost and latency). The classification was fuzzy and data integrity depended on getting that one-shot guess right, so the routing prompt was biased toward dense, over-escalating and burning cost. Every table now takes the same path — cheap parse → audit critic → in-place refinement with tools — with a best-draft-wins guarantee, so a misclassification can no longer lose data. On an 18-document Iberia benchmark refine was 27% cheaper (cheaper on every doc) and substantially faster on the slow tail (worst single doc 1346s → 317s), with all four full_content quality metrics improving. As part of the change the routing prompt now requires ≥2 rows × ≥2 columns of tabular evidence before sending a block to the table pipeline, so label/value pairs, signatures and logos are no longer misrouted as tables.
[Improved + API] Workflow analytics timeseries response is much smaller and faster
GET /api/v3/workflows/{id}/analytics/timeseries now serves the default monitoring view in a few KB of gzipped JSON instead of 1.7–3.9 MB. The “Overall” series across all fields is shipped pre-collapsed as a new overall_rows array (one entry per extraction), and an opt-in repeatable field_ids query parameter narrows the legacy rows payload to only the fields you want — present-but-empty (?field_ids=) returns counts and overall_rows only. Calls that omit field_ids still receive the full legacy rows, so existing integrations keep working unchanged. The cold-path recomputation now scopes the live aggregation to cold versions instead of the full workflow, self-heals to all-warm on the first full-universe read, and the response is gzip-compressed.
June 10, 2026
[New] Per-user timezone preference for API datetimes User profiles now carry an IANA timezone preference, defaulting toEurope/Madrid. Datetime fields in API responses are serialized in the user’s chosen zone, and the dashboard Account page gains a searchable timezone selector that persists through the existing profile-update flow. Storage stays UTC end-to-end — only the wire serialization (and transitively, what the dashboard renders) reflects the preference.
[Fixed + API] Oversized request bodies now return 400 instead of 500
Any v3 endpoint that reads a request body returned a 500 when the JSON payload exceeded the 20 MB limit (DATA_UPLOAD_MAX_MEMORY_SIZE): Django raised RequestDataTooBig while django-ninja read request.body during parameter resolution, before the view ever ran, so it could not be caught inside the endpoint. A global exception handler on the v3 API now returns 400 with {"detail": "Request body is too large. Maximum allowed size is 20 MB."}. The limit in the message is derived from the setting, so it stays accurate if the cap is tuned.
[API] Rate limiting now enforced on the external API in production
The external API’s rate limiter is now active in production. It had been silently disabled for roughly three months because RATE_LIMIT_ENABLED and REDIS_URL were added to the dev config but never propagated to prod. Callers exceeding the configured limits may now receive 429 responses.
[Fixed] Verify Document button now works on split + extract workflows
The header Verify Document button used to be a permanent no-op on workflows that combine a Split node with an Extract: it targeted the file-level extraction, but extracted data on a split lives on per-split child extractions, so verifying the parent verified nothing. The button now targets the active split’s child extraction (and is labelled Verify Extraction on a split tab to match), and is disabled on parse, split, and validation tabs where there is nothing to verify. A split file is reported as verified only once every non-empty child extraction is verified — empty category tabs no longer wedge the rollup. Shift+Enter advances through unverified splits before moving on to the next file.
[Improved + API] New verifier API surface; validator deprecated
The “human-validation → verification” rename now reaches the file-level API. A new endpoint POST / PATCH /api/v2/.../files/{id}/verifier/ assigns and updates the verifier for a file, a verifier__in filter narrows file lists by assigned verifier, and ?include=verifier returns the assigned verifier in file responses. The legacy /validator action, validator__in filter, and ?include=validator option remain live for backward compatibility but are now flagged deprecated in the OpenAPI spec; integrations should plan the move.
[Fixed + API] Non-member reads of workflow analytics now return 404
GET /api/v3/workflows/{id}/analytics/summary and GET /api/v3/workflows/{id}/analytics/timeseries previously returned 403 Forbidden to a caller outside the workflow’s organisation, leaking the existence of the workflow. They now return 404 Not Found, matching GET /api/v3/workflows/{id}. Members continue to receive analytics as before.
[Fixed] New free accounts now see their signup credits on the Usage page
The Usage page counted only purchased and granted credits, so a brand-new free account — whose only balance is its signup bonus — showed a zero balance and the empty-state screen instead of the credits it could actually spend. Signup credits are now included in both the headline total and the per-source breakdown.
June 9, 2026
[API] New v3 endpoints for row-level edits on object fields The v3 API now exposes positional row operations on object-field tables: add, delete, swap, verify-row, and verify-all. Rows are addressed positionally by(extraction_uuid, field_persistent_id, index) instead of an opaque datarow id, and the dense [0, n_live] invariant is enforced at the boundary (negative indexes now return 422 instead of a silent 404). The corresponding v2 actions on DatarowViewSet are now marked deprecated but remain live; integrations should plan to migrate.
[Improved] Document parsing now retries transient LLM failures more reliably
The parse pipeline now retries LengthFinishReasonError (model hit max-tokens, often non-deterministic) and detects silent parse failures where the LLM returned a 200 but the response could not be coerced into the schema. Both previously skipped straight to the fallback model on the first attempt; they now go through the configured retry budget on the primary model first. The per-page parsing token budget was also raised from 8K to 32K to reduce length-truncation failures, and ParseFailedError now carries the underlying per-page error detail.
[Fixed] Lookup + Validate workflows now complete end-to-end
Two independent bugs that broke the same Lookup + Validate workflow are fixed:
- A
Validatenode downstream of anExtractnode with lookup fields previously returned every rule as inconclusive (“No extraction data available to validate.”), because the validate step resolved its upstream to a synthetic lookup node and read a state key nothing ever wrote. It now resolves through smart-lookup hops back to the real producing extract. - The Gemini page-sum verification schema was missing a top-level
title, which made model-build raise outside the per-tasktry/exceptand aborted the whole extraction whenever Phase-1 sum verification failed and Gemini verification was attempted.
Extraction.uuidandFile.nameget btree indexes (builtCONCURRENTLY), eliminating sequential scans on the hundreds of lookups per day they served.- A new composite
FileCollection(workflow_id, created_at DESC)index backs the per-workflow collection listing. created_atdate filters across the admin dashboard, billing workflow-usage queries, and the publicResultsFilterare rewritten from(created_at AT TIME ZONE 'UTC')::datecasts (which disabled the index, costing up to ~2s on a single filter path) to half-opencreated_atranges. Behavior is unchanged because the deployment timezone is UTC.
June 5, 2026
[Improved] Transient frontend request failures now auto-retry with backoff The web app retries idempotent React Query requests that fail with transient connectivity errors or 5xx responses, using capped exponential backoff (up to ~10s) and up to 3 attempts. Deterministic 4xx responses and unknown errors still fail fast. Connectivity errors are now detected via the Fetch spec’sTypeError instead of message matching, which cuts down on false-positive error reports during brief network blips.
[API] Malformed Authorization: Bearer headers now return 401
Authorization headers with malformed bearer tokens (for example, an empty token or whitespace inside the token value) now return 401 Unauthorized instead of 500 Internal Server Error. The successful auth path and well-formed token-rejection responses are unchanged.
[Fixed] Newly created workflows lay out their nodes with wider default spacing
Newly created workflow nodes are placed with a larger default gap so the auto-layout reads more clearly out of the box and matches the spacing the rest of the editor assumes.
[Fixed] Full-page loading spinner no longer jumps during initial load
The full-page spinner stays anchored in place while the app boots, instead of shifting position as the surrounding layout settles in.
[New] How credits work — new pricing & credits docs page
A new Concepts → Account page, How credits work, documents per-operator credit costs (including Validate at 5 credits per rule per page), plan €/credit conversion, included credits, usage tracking, and tips for keeping cost down. The Usage & Billing page links to it in place of the previous “contact us for pricing” stub.
[Improved] Monitoring over-time chart now plots on a true time axis
The accuracy and confidence over-time chart on the Monitoring tab positions each extraction at its real timestamp on a time-scaled axis, instead of forcing points onto an evenly-spaced grid. Time-bucketing now adapts to the visible span, so long, dense histories coarsen gracefully while short spans stay at full resolution. Version markers sit at their exact creation time and merge into a single ranged label (for example v1.0–v1.2) when several versions ship the same day, and the empty states now distinguish between no rows, no reviewed extractions, and no recorded confidence scores.
[Improved] Large workflow versions no longer time out when loading file counts
Loading the file counts for a high-volume workflow version no longer tips over the request timeout. The count is now served by a single annotated aggregate backed by an index-only scan, replacing the per-file status scan that caused worker timeouts on workflows with many files.
[Fixed] Filtering and sorting restored on smart-lookup columns
Columns backed by a smart-lookup field keep their sort and filter controls in the data viewer while still showing the lookup indicator. Previously the custom lookup header replaced the entire header cell, hiding the filter button on those columns even though filtering still worked underneath.
[Fixed] Overlapping bounding-box highlights no longer wash out the PDF page
In the PDF visualizer, hovering a stack of overlapping bounding boxes now reads as a single translucent tint on both light and dark pages, instead of compounding toward opaque as more boxes overlapped. Boxes shared by many fields, such as table sections, also render more efficiently.
June 2, 2026
[Improved] Monitoring page now reveals data progressively Each card on the Monitoring tab paints its title and controls immediately and shows a skeleton only for the body that is still loading, off the data source that feeds it. A fast endpoint can paint without waiting on a slower sibling, instead of the whole page sitting behind a single page-level skeleton. The time-series chart also now keeps daily resolution on 30-day ranges instead of collapsing into ~5 weekly points. [Improved] Faster workflow version analytics summaryGET /api/v3/workflows/{id}/analytics/summary is significantly faster on high-volume workflow versions. Overall metrics, per-field metrics, verified counts, and throughput are now computed in two table scans instead of five, with no change to the response shape.
Technical detail
Technical detail
The endpoint previously routed through the paginated read path and ran the per-extraction aggregate twice plus a discarded pagination subquery. It now serves the summary from a dedicated read that combines the by-field
GROUP BY and a single per-extraction COUNT(...) FILTER(...) aggregate, with the paginated read path unchanged for the timeseries and detail endpoints.GET /api/v3/workflows/{id}/analytics/timeseries is now significantly faster on high-volume workflow versions, applying the same optimization as the summary endpoint. The response shape is unchanged.
Technical detail
Technical detail
The timeseries endpoint consumes only the per-extraction detail rows, but was still served by the full five-scan paginated read — computing the overall sum, per-field
GROUP BY, and through-rate counts only to discard them. It now uses a detail-only read, and resolves the latest N extractions from the Extraction table (which owns processed_at) instead of grouping the multi-million-row analytics cache — roughly 34× faster on the top-N lookup, with existing indexes only and no migration./analytics endpoint removed
The deprecated GET /api/v3/workflows/{id}/analytics route (flagged for removal when the frontend migration completed) is gone. Use GET /analytics/summary for per-version overall and per-field metrics, and GET /analytics/timeseries for the cross-version series.
[Fixed] File validation status stays in sync with its datapoints
When you confirm the last unverified datapoint on a file, the file badge now reliably flips to Validated, and when you unverify a previously-verified datapoint the file rolls back to Processed. Previously, the file-level validation status could drift from its datapoints — most commonly, the last cell confirmation would not flip the file badge.
[Improved] Organization switcher now scales to many organizations
The sidebar organization switcher caps at 10 visible organizations by default with a Show all expander, and adds a search box that fuzzy-matches organization names across your full list. Organizations you’re a direct member of are prioritized over support-access ones, and a per-user most-recently-used ordering surfaces the organizations you actually work in first. The current organization is always shown.
[Fixed] Deep links to a file’s results now switch to the workflow’s organization
Opening a workflow file via a direct URL or in a new tab (middle-click) now elevates the active organization to the one the workflow belongs to, matching click-through navigation. Previously a cold-loaded file results page kept the UI on your own organization, hiding the support-access banner and showing the wrong organization in the sidebar.
[Fixed] Organization selector rows stay a readable size on long lists
When you belong to many organizations, each row in the sidebar organization selector keeps a consistent, tappable height with visible spacing, instead of compressing to near edge-to-edge as the list grows.
June 1, 2026
[New] Workflow version selector The workflow page header now has a version selector (on the Studio and Monitoring tabs) so you can view any saved version of a workflow. Selecting a non-latest version on Studio disables editing with an explainer — only the latest version can be edited — while Monitoring scopes its analytics to the version you pick. [New] Per-version monitoring history The Monitoring tab now shows version history: the accuracy/confidence chart spans all versions with markers at each version change, a changelog panel lists what changed, and KPIs and by-field metrics can be scoped to a selected version.Technical detail
Technical detail
The analytics endpoint was split into purpose-built reads —
GET /analytics/summary?version_id= (per-version overall + per-field means and through-rate counts) and GET /analytics/timeseries?limit= (cross-version series) — and analytics are now paginated by extraction rather than by (extraction, field) row, so wide schemas no longer collapse the window. The old /analytics endpoint is deprecated but retained until the frontend migration completes.anyformat
The official Python SDK’s PyPI distribution has been renamed from anyformat-sdk to anyformat — pip install anyformat — mirroring @anyformat/sdk on npm. The import path is unchanged (from anyformat.sdk import Client).
Technical detail
Technical detail
The distribution starts at version 0.6.0 (the next free slot past the legacy
anyformat==0.5.0 on PyPI). The internal CLI binary that previously occupied the anyformat console-script slot was renamed to af; from anyformat.cli import ... still works.
