Reading Workflow Call Outputs

Where the extracted data lives in a POST /v3/workflows/{name}/call response — the path, the per-event-type field map, and accessor patterns in every SDK

Hand off to an LLM

When you call a workflow with wait=true (or fetch a completed call via GET /v3/calls/{callID}), the response is the same shape — a call object whose terminal events are in call.outputs. The extracted data lives on each event, not under a nested transformation field.

result (or response object)
  └─ call
       ├─ callID
       ├─ status                ("completed" | "pending" | "running" | "failed")
       ├─ outputs[]             array of terminal events
       │    ├─ eventID
       │    ├─ eventType        discriminator → tells you which payload field to read
       │    ├─ functionName
       │    └─ <payload>        transformedContent | enrichedContent | choice | …
       ├─ errors[]
       ├─ url                   /v3/calls/{callID}
       └─ traceUrl              /v3/calls/{callID}/trace  (full per-node execution graph)

outputs is always an array

The terminal nodes of a workflow are whatever has no outgoing edge. A single-step workflow has exactly one terminal event; a branching workflow (Classify) or a fan-out workflow (Split) can have more. Even with one terminal node, the field is plural — you index into it.

result.call.outputs[0].transformedContent  // ✓
result.call.output                          // ✗ does not exist
result.call.outputs.transformedContent      // ✗ outputs is an array

Where the data lives, by eventType

The payload field name depends on the event variant. Switch on eventType (or use the SDK's variant accessor) to get the right field.

eventTypeContent fieldProduced by
extracttransformedContentExtract function
enrichenrichedContent (carries upstream content + matched data)Enrich function
jointransformedContentJoin function
classifychoice (the picked classification label) — the extracted JSON flows from the routed-to extractor downstreamClassify function
transform, analyzetransformedContentLegacy V1/V2 functions (still readable from V3)
routeroute metadataLegacy V1/V2 function
senddelivery status (no content payload)Send function
parseoutput is queryable via POST /v3/fs, not embedded in the call responseParse function
errorerrorMessage, errorCode, errorDetailsAny failed function call

So an Extract-only workflow lands at outputs[0].transformedContent. An Extract → Enrich workflow lands at outputs[0].enrichedContent (because Enrich is the terminal node). A Classify-only workflow lands at outputs[0].choice.

Accessor patterns

The same path in every language. The SDKs flatten the event variants into a single discriminated union — you can either read the field directly (it'll be null/missing on the wrong variant) or use the SDK's variant accessor for type safety.

# Extract-only workflow
jq '.call.outputs[0].transformedContent' < response.json

# Extract → Enrich workflow
jq '.call.outputs[0].enrichedContent' < response.json

# Switch on eventType
jq '.call.outputs[] | if .eventType=="enrich" then .enrichedContent else .transformedContent end' < response.json
const { call } = await client.workflows.call("my-workflow", { wait: true, /* ... */ });

// Single terminal node (Extract)
const data = call?.outputs?.[0]?.transformedContent;

// Single terminal node (Enrich)
const enriched = call?.outputs?.[0]?.enrichedContent;

// Generic — switch on eventType
for (const event of call?.outputs ?? []) {
  switch (event.eventType) {
    case "extract":
    case "transform":
    case "join":
      console.log(event.transformedContent);
      break;
    case "enrich":
      console.log(event.enrichedContent);
      break;
    case "classify":
      console.log(event.choice);
      break;
  }
}
response = client.workflows.call("my-workflow", wait=True, ...)
call = response.call

# Single terminal node (Extract)
data = call.outputs[0].transformed_content

# Single terminal node (Enrich)
enriched = call.outputs[0].enriched_content

# Generic — switch on event_type
for event in call.outputs:
    if event.event_type in ("extract", "transform", "join"):
        print(event.transformed_content)
    elif event.event_type == "enrich":
        print(event.enriched_content)
    elif event.event_type == "classify":
        print(event.choice)
resp, err := client.Workflows.Call(ctx, "my-workflow", bem.WorkflowCallParams{Wait: bem.Bool(true), /* ... */})
if err != nil { panic(err) }

// Direct field access (works because the union flattens all variant fields)
data := resp.Call.Outputs[0].TransformedContent  // for extract / transform / join
enriched := resp.Call.Outputs[0].EnrichedContent // for enrich

// Type-safe variant switch
for _, event := range resp.Call.Outputs {
    switch v := event.AsAny().(type) {
    case bem.EventExtract:
        fmt.Println(v.TransformedContent)
    case bem.EventEnrich:
        fmt.Println(v.EnrichedContent)
    case bem.EventClassify:
        fmt.Println(v.Choice)
    }
}
var response = await client.Workflows.Call("my-workflow", new WorkflowCallParams { Wait = true, /* ... */ });
var call = response.Call;

// Single terminal node (Extract)
var data = call.Outputs[0].TransformedContent;

// Single terminal node (Enrich)
var enriched = call.Outputs[0].EnrichedContent;

// Generic — pattern match on event variant
foreach (var ev in call.Outputs)
{
    switch (ev)
    {
        case ExtractEvent ex:   Console.WriteLine(ex.TransformedContent); break;
        case EnrichEvent en:    Console.WriteLine(en.EnrichedContent);    break;
        case ClassifyEvent cl:  Console.WriteLine(cl.Choice);             break;
    }
}
bem workflows call --workflow-name my-workflow --wait --call-reference-id my-ref \
  --input.single-file '{"inputContent": "@my-file.pdf", "inputType": "pdf"}' \
  --transform 'call.outputs.0.transformedContent'

The --transform flag uses GJSON syntax and projects to the path you ask for, so the CLI prints just the extracted JSON. Replace transformedContent with enrichedContent for an Enrich-terminal workflow.

Anti-patterns

  • call.output (singular) — does not exist. The field is always outputs[].
  • outputs[0].transformation.extractedJSON — that's the legacy V1/V2 Transformation record shape returned by GET /v1-beta/transformations. V3 workflow calls return events whose payload sits at the top of each event object as transformedContent / enrichedContent / choice / etc.
  • Skipping the outputs indexoutputs is an array even when there's a single terminal event.
  • Reading transformedContent on an enrich event — empty or missing. Enrich's payload is at enrichedContent. Switch on eventType.

For per-node execution detail (every intermediate function call, not just the terminal nodes), fetch GET /v3/calls/{callID}/trace.

On this page