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
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 arrayWhere 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.
eventType | Content field | Produced by |
|---|---|---|
extract | transformedContent | Extract function |
enrich | enrichedContent (carries upstream content + matched data) | Enrich function |
join | transformedContent | Join function |
classify | choice (the picked classification label) — the extracted JSON flows from the routed-to extractor downstream | Classify function |
transform, analyze | transformedContent | Legacy V1/V2 functions (still readable from V3) |
route | route metadata | Legacy V1/V2 function |
send | delivery status (no content payload) | Send function |
parse | output is queryable via POST /v3/fs, not embedded in the call response | Parse function |
error | errorMessage, errorCode, errorDetails | Any 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.jsonconst { 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 alwaysoutputs[].outputs[0].transformation.extractedJSON— that's the legacy V1/V2 Transformation record shape returned byGET /v1-beta/transformations. V3 workflow calls return events whose payload sits at the top of each event object astransformedContent/enrichedContent/choice/ etc.- Skipping the
outputsindex —outputsis an array even when there's a single terminal event. - Reading
transformedContenton an enrich event — empty or missing. Enrich's payload is atenrichedContent. Switch oneventType.
For per-node execution detail (every intermediate function call, not just the terminal nodes), fetch GET /v3/calls/{callID}/trace.
Related
System Overview
Functions, workflows, calls, events, transformations, subscriptions, views — and how they fit together
Polling and retries
Sync waits, polling, idempotency via callReferenceID
Webhooks
Subscribe an endpoint and verify signed deliveries
Call a Workflow API
POST /v3/workflows/{workflowName}/call — every parameter