LLM-readable documentation index

V3 Migration

What changed between the legacy API and V3, and what you need to do

Hand off to an LLM

V3 cleans up the function and routing primitives that grew up across V1 and V2. The mental model didn't change — bem still runs functions inside workflows — but a handful of names and endpoints did. Existing integrations keep working. Legacy types remain readable and callable; you only need to migrate when you create a new function or add a new endpoint.

What renamed

ConceptLegacyV3
Pull structured data out of a file (text-first)transform functionextract function
Pull structured data out of a file (visual-first)analyze functionextract function (same primitive — input drives the strategy)
Branch on content typeroute functionclassify function
The list of branches on a route/classifyroutesclassifications
Send a request to bempipeline (V1) / function-call (V2)workflow + call
The output payload of a successful functiontransformationtransformation (still the same name on the wire — extract events emit eventType: "transform" for backward compatibility)

What's the same

  • Functions are still versioned. Updates create new versions; old versions remain immutable and addressable.
  • outputSchema still uses standard JSON Schema.
  • Authentication is still an x-api-key header.
  • Webhooks still use the bem-signature header (t={timestamp},v1={hex_hmac_sha256}).
  • Cursor pagination still uses startingAfter and endingBefore.
  • File inputType values (csv, docx, email, heic, heif, html, jfif, jpeg, json, m4a, mov, mp3, mp4, pdf, png, pptx, text, wav, webp, xls, xlsx, xml) carry over unchanged.

Endpoint mapping

The /v3 surface replaces a few legacy entry points. Where you used to call the legacy URL on the left, call the V3 URL on the right.

LegacyV3Notes
POST /v2/functions/{functionName}/callPOST /v3/workflows/{workflowName}/callV3 always invokes through a workflow. For a single-function pipeline, wrap the function in a one-node workflow with no edges.
POST /v2/callsPOST /v3/workflows/{workflowName}/callPer-workflow URL replaces the unified /calls body.
GET /v2/calls/{id}GET /v3/calls/{callID}Same shape, V3 wraps the call object consistently.
POST /v1-beta/transformationsPOST /v3/workflows/{workflowName}/callTransformations are now produced by workflow calls.
POST /v2/connectorsinline connectors on POST /v3/workflowsConnectors are workflow configuration, not a separate resource.
POST /v2/collections (Bearer auth)POST /v3/collections (x-api-key)Collections moved under V3. The item body field is now data (string or object), not content + metadata.

Subscriptions are available at /v3/subscriptions. The legacy /v1-alpha/subscriptions endpoint still works for backward compatibility and routes to the same handler. See Webhooks for the end-to-end flow.

Migration checklist

  1. Create new functions as extract or classify. Don't reach for transform/analyze/route — those types are read-only on V3.
  2. Wrap existing functions in workflows if you're calling them directly. A one-node workflow has no edges and behaves identically:
    {
      "name": "invoice-extractor-wrapper",
      "mainNodeName": "invoice-extractor",
      "nodes": [
        {
          "name": "invoice-extractor",
          "function": { "name": "invoice-extractor" }
        }
      ]
    }
    Adding nodes later produces a new workflow version without breaking existing callers.
  3. Update branching keys. When you build a workflow off a Classify function, the edge field is destinationName and it matches classifications[].name. (In legacy, this was routes[].name on route functions.)
  4. Re-point your polling URL if you read call status: legacy GET /v2/calls/{id} → V3 GET /v3/calls/{callID}.
  5. Switch collections endpoints from /v2/collections (with Authorization: Bearer) to /v3/collections (with x-api-key), and update item bodies to use data instead of content + metadata.

You don't need to migrate a function just to use the new APIs around it: V3 endpoints accept legacy function types for reads and (for extract-equivalent operations) calls. The only hard line is creates and updates — those are V3 types only.

Scoring: eval runs and comparisons

The scoring endpoints are new in V3, so there is nothing to migrate — but they work differently enough from the legacy accuracy tooling to be worth reading before you wire them up.

A run is the extraction; the score is a read. Creating an eval run or a comparison dispatches function calls and nothing more. Comparing that output against your expected values is a pure function of two values bem already stores, so it happens on every read instead of being fixed when the run executed. Re-reading a finished run costs no model calls, and its numbers always reflect your dataset as it stands now.

That means comparison settings are query parameters, not request-body fields:

GET /v3/model-comparisons/{comparisonID}?matchMode=normalized&orderMatching=false
  • matchModestrict (default), normalized, or fuzzy. Anything else is a 400; an unrecognized mode is rejected rather than scored at some other strictness. This is the whole of strictness. normalized compares numbers as numbers and dates by calendar value rather than by spelling; fuzzy adds a similarity pass over free text. There are deliberately no tolerance or similarity dials: a threshold you can widen until the numbers look acceptable is not measuring anything.
  • orderMatching — score array elements in order instead of as sets.
  • includeRowResults — include per-row, per-field detail. Off by default; it can be large.

GET /v3/eval/score/{scoreRunID} takes no parameters at all. Its comparison is exact: a value matches the expected one or it does not.

Reading a field result

Both endpoints classify each leaf the same way:

CategoryMeaning
matchboth present and equal
mismatchboth present, different
missingexpected a value, none was produced
extraa value was produced where none was expected

Only match counts toward precision, recall and f1. A field result also carries delta for every non-identical numeric pair and similarity (a Levenshtein ratio) for every non-identical string pair — they tell you how close a wrong value was, which never makes it right.

For the full workflow — building a dataset, comparing versions, reading lift, and spotting mislabeled rows — see Comparing Functions on a Dataset.

What's still legacy

Legacy reference pages remain available under API Reference → Legacy for endpoints that don't have a V3 successor yet. Most function-calls, events, pipelines, connectors, actions, and subscriptions endpoints are accessible there. Each legacy page that does have a V3 successor carries a deprecation callout linking to it.

On this page