Quickstart

Make your first synchronous workflow call with bem

Hand off to an LLM

This guide walks you through running your first bem workflow end to end. By the end you'll have:

  1. An extract function with a JSON schema describing the structure you want to pull out
  2. A workflow that wires that function into a reusable entry point
  3. A synchronous call that returns your structured data in one request (wait=true)

Pick a language from the tabs in each step — the flow is identical across cURL, the SDKs, and the CLI.

Prerequisites

  • A bem account (free to sign up)
  • An API key generated from Settings → API Keys in the bem UI

Step 1: Set your API key

Export your API key so the SDKs, CLI, and curl examples can pick it up from the environment:

export BEM_API_KEY='your-api-key-here'

To persist the key across shell sessions, add that line to your shell profile (such as ~/.zshrc or ~/.bashrc).

Step 2: Install the SDK

Skip this step if you're using curl.

curl ships with macOS and most Linux distributions — no install required.

npm install bem-ai-sdk
pip install bem-sdk
go get github.com/bem-team/bem-go-sdk
dotnet add package Bem
brew install bem-team/tools/bem

Or, with Go:

go install github.com/bem-team/bem-cli/cmd/bem@latest

Step 3: Create an extract function

Extract functions pull structured JSON out of unstructured files. The outputSchema below describes the fields we want to extract from an invoice — you can customize it to match any document type.

curl -X POST https://api.bem.ai/v3/functions \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "functionName": "invoice-extractor",
    "type": "extract",
    "displayName": "Invoice Extractor",
    "outputSchemaName": "Invoice",
    "outputSchema": {
      "type": "object",
      "required": ["invoiceNumber", "vendor", "totalAmount"],
      "properties": {
        "invoiceNumber": { "type": "string", "description": "Unique invoice identifier" },
        "invoiceDate":   { "type": "string", "description": "Invoice date (YYYY-MM-DD)" },
        "dueDate":       { "type": "string", "description": "Payment due date (YYYY-MM-DD)" },
        "vendor": {
          "type": "object",
          "properties": {
            "name":    { "type": "string" },
            "address": { "type": "string" }
          }
        },
        "lineItems": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "description": { "type": "string" },
              "quantity":    { "type": "number" },
              "unitPrice":   { "type": "number" },
              "amount":      { "type": "number" }
            }
          }
        },
        "subtotal":    { "type": "number" },
        "taxAmount":   { "type": "number" },
        "totalAmount": { "type": "number" }
      }
    }
  }'

Save as create-function.ts:

import Bem from "bem-ai-sdk";

const client = new Bem();

const { function: fn } = await client.functions.create({
  functionName: "invoice-extractor",
  type: "extract",
  displayName: "Invoice Extractor",
  outputSchemaName: "Invoice",
  outputSchema: {
    type: "object",
    required: ["invoiceNumber", "vendor", "totalAmount"],
    properties: {
      invoiceNumber: { type: "string", description: "Unique invoice identifier" },
      invoiceDate:   { type: "string", description: "Invoice date (YYYY-MM-DD)" },
      dueDate:       { type: "string", description: "Payment due date (YYYY-MM-DD)" },
      vendor: {
        type: "object",
        properties: {
          name:    { type: "string" },
          address: { type: "string" },
        },
      },
      lineItems: {
        type: "array",
        items: {
          type: "object",
          properties: {
            description: { type: "string" },
            quantity:    { type: "number" },
            unitPrice:   { type: "number" },
            amount:      { type: "number" },
          },
        },
      },
      subtotal:    { type: "number" },
      taxAmount:   { type: "number" },
      totalAmount: { type: "number" },
    },
  },
});

console.log(fn);

Run it with npx tsx create-function.ts.

Save as create_function.py:

from bem import Bem

client = Bem()

response = client.functions.create(
    function_name="invoice-extractor",
    type="extract",
    display_name="Invoice Extractor",
    output_schema_name="Invoice",
    output_schema={
        "type": "object",
        "required": ["invoiceNumber", "vendor", "totalAmount"],
        "properties": {
            "invoiceNumber": {"type": "string", "description": "Unique invoice identifier"},
            "invoiceDate":   {"type": "string", "description": "Invoice date (YYYY-MM-DD)"},
            "dueDate":       {"type": "string", "description": "Payment due date (YYYY-MM-DD)"},
            "vendor": {
                "type": "object",
                "properties": {
                    "name":    {"type": "string"},
                    "address": {"type": "string"},
                },
            },
            "lineItems": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "description": {"type": "string"},
                        "quantity":    {"type": "number"},
                        "unitPrice":   {"type": "number"},
                        "amount":      {"type": "number"},
                    },
                },
            },
            "subtotal":    {"type": "number"},
            "taxAmount":   {"type": "number"},
            "totalAmount": {"type": "number"},
        },
    },
)
print(response.function)

Run it with python create_function.py.

Save as create_function.go:

package main

import (
    "context"
    "fmt"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    schema := map[string]any{
        "type":     "object",
        "required": []string{"invoiceNumber", "vendor", "totalAmount"},
        "properties": map[string]any{
            "invoiceNumber": map[string]any{"type": "string"},
            "invoiceDate":   map[string]any{"type": "string"},
            "dueDate":       map[string]any{"type": "string"},
            "vendor": map[string]any{
                "type": "object",
                "properties": map[string]any{
                    "name":    map[string]any{"type": "string"},
                    "address": map[string]any{"type": "string"},
                },
            },
            "lineItems": map[string]any{
                "type": "array",
                "items": map[string]any{
                    "type": "object",
                    "properties": map[string]any{
                        "description": map[string]any{"type": "string"},
                        "quantity":    map[string]any{"type": "number"},
                        "unitPrice":   map[string]any{"type": "number"},
                        "amount":      map[string]any{"type": "number"},
                    },
                },
            },
            "subtotal":    map[string]any{"type": "number"},
            "taxAmount":   map[string]any{"type": "number"},
            "totalAmount": map[string]any{"type": "number"},
        },
    }

    resp, err := client.Functions.New(context.TODO(), bem.FunctionNewParams{
        CreateFunction: bem.CreateFunctionUnionParam{
            OfExtract: &bem.CreateFunctionExtractParam{
                FunctionName:     "invoice-extractor",
                DisplayName:      bem.String("Invoice Extractor"),
                OutputSchemaName: bem.String("Invoice"),
                OutputSchema:     schema,
            },
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", resp.Function)
}

Run it with go run create_function.go.

Save as CreateFunction.cs:

using System.Text.Json;
using Bem;
using Bem.Models.Functions;

BemClient client = new();

var schemaJson = """
{
  "type": "object",
  "required": ["invoiceNumber", "vendor", "totalAmount"],
  "properties": {
    "invoiceNumber": { "type": "string", "description": "Unique invoice identifier" },
    "invoiceDate":   { "type": "string", "description": "Invoice date (YYYY-MM-DD)" },
    "dueDate":       { "type": "string", "description": "Payment due date (YYYY-MM-DD)" },
    "vendor": {
      "type": "object",
      "properties": {
        "name":    { "type": "string" },
        "address": { "type": "string" }
      }
    },
    "lineItems": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": { "type": "string" },
          "quantity":    { "type": "number" },
          "unitPrice":   { "type": "number" },
          "amount":      { "type": "number" }
        }
      }
    },
    "subtotal":    { "type": "number" },
    "taxAmount":   { "type": "number" },
    "totalAmount": { "type": "number" }
  }
}
""";

var response = await client.Functions.Create(new FunctionCreateParams
{
    CreateFunction = new Extract
    {
        FunctionName     = "invoice-extractor",
        DisplayName      = "Invoice Extractor",
        OutputSchemaName = "Invoice",
        OutputSchema     = JsonSerializer.Deserialize<JsonElement>(schemaJson),
    },
});

Console.WriteLine(response.Function);

Run it from the project directory with dotnet run.

Put the schema in invoice-schema.json:

{
  "type": "object",
  "required": ["invoiceNumber", "vendor", "totalAmount"],
  "properties": {
    "invoiceNumber": { "type": "string" },
    "invoiceDate":   { "type": "string" },
    "dueDate":       { "type": "string" },
    "vendor": {
      "type": "object",
      "properties": {
        "name":    { "type": "string" },
        "address": { "type": "string" }
      }
    },
    "lineItems": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": { "type": "string" },
          "quantity":    { "type": "number" },
          "unitPrice":   { "type": "number" },
          "amount":      { "type": "number" }
        }
      }
    },
    "subtotal":    { "type": "number" },
    "taxAmount":   { "type": "number" },
    "totalAmount": { "type": "number" }
  }
}

Then create the function:

bem functions create \
  --function-name invoice-extractor \
  --type extract \
  --display-name "Invoice Extractor" \
  --output-schema-name Invoice \
  --output-schema @invoice-schema.json

Response:

{
  "function": {
    "functionID": "fn_2abc123xyz",
    "functionName": "invoice-extractor",
    "displayName": "Invoice Extractor",
    "type": "extract",
    "currentVersionNum": 1
  }
}
{
  functionID: 'fn_2abc123xyz',
  functionName: 'invoice-extractor',
  displayName: 'Invoice Extractor',
  type: 'extract',
  currentVersionNum: 1
}
Function(
    function_id='fn_2abc123xyz',
    function_name='invoice-extractor',
    display_name='Invoice Extractor',
    type='extract',
    current_version_num=1,
)
bem.Function{
    FunctionID:        "fn_2abc123xyz",
    FunctionName:      "invoice-extractor",
    DisplayName:       "Invoice Extractor",
    Type:              "extract",
    CurrentVersionNum: 1,
}
Bem.Models.Functions.Function {
  FunctionID        = "fn_2abc123xyz",
  FunctionName      = "invoice-extractor",
  DisplayName       = "Invoice Extractor",
  Type              = "extract",
  CurrentVersionNum = 1
}
{
  "function": {
    "functionID": "fn_2abc123xyz",
    "functionName": "invoice-extractor",
    "displayName": "Invoice Extractor",
    "type": "extract",
    "currentVersionNum": 1
  }
}

Step 4: Create a workflow

A workflow wires one or more functions into a reusable entry point. For a single-function pipeline you only need one node and no edges.

curl -X POST https://api.bem.ai/v3/workflows \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "name": "invoice-processing",
    "displayName": "Invoice Processing Workflow",
    "tags": ["invoices", "financial-data"],
    "mainNodeName": "invoice-extractor",
    "nodes": [
      {
        "name": "invoice-extractor",
        "function": { "name": "invoice-extractor" }
      }
    ]
  }'
import Bem from "bem-ai-sdk";

const client = new Bem();

const { workflow } = await client.workflows.create({
  name: "invoice-processing",
  displayName: "Invoice Processing Workflow",
  tags: ["invoices", "financial-data"],
  mainNodeName: "invoice-extractor",
  nodes: [
    {
      name: "invoice-extractor",
      function: { name: "invoice-extractor" },
    },
  ],
});

console.log(workflow);
from bem import Bem

client = Bem()

response = client.workflows.create(
    name="invoice-processing",
    display_name="Invoice Processing Workflow",
    tags=["invoices", "financial-data"],
    main_node_name="invoice-extractor",
    nodes=[
        {
            "name": "invoice-extractor",
            "function": {"name": "invoice-extractor"},
        }
    ],
)
print(response.workflow)
package main

import (
    "context"
    "fmt"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    resp, err := client.Workflows.New(context.TODO(), bem.WorkflowNewParams{
        Name:         "invoice-processing",
        DisplayName:  bem.String("Invoice Processing Workflow"),
        Tags:         []string{"invoices", "financial-data"},
        MainNodeName: "invoice-extractor",
        Nodes: []bem.WorkflowNewParamsNode{
            {
                Name: bem.String("invoice-extractor"),
                Function: bem.FunctionVersionIdentifierParam{
                    Name: bem.String("invoice-extractor"),
                },
            },
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", resp.Workflow)
}
using Bem;
using Bem.Models.Workflows;

BemClient client = new();

var response = await client.Workflows.Create(new WorkflowCreateParams
{
    Name         = "invoice-processing",
    DisplayName  = "Invoice Processing Workflow",
    Tags         = new List<string> { "invoices", "financial-data" },
    MainNodeName = "invoice-extractor",
    Nodes = new List<Node>
    {
        new Node
        {
            Function = new FunctionVersionIdentifier { Name = "invoice-extractor" },
            Name     = "invoice-extractor",
        },
    },
});

Console.WriteLine(response.Workflow);
bem workflows create \
  --name invoice-processing \
  --display-name "Invoice Processing Workflow" \
  --tags '["invoices", "financial-data"]' \
  --main-node-name invoice-extractor \
  --node '{name: invoice-extractor, function: {name: invoice-extractor}}'

Response:

{
  "workflow": {
    "id": "wf_2def456abc",
    "name": "invoice-processing",
    "displayName": "Invoice Processing Workflow",
    "versionNum": 1,
    "mainNodeName": "invoice-extractor",
    "nodes": [
      {
        "name": "invoice-extractor",
        "function": {
          "name": "invoice-extractor",
          "versionNum": 1
        }
      }
    ],
    "edges": []
  }
}
{
  id: 'wf_2def456abc',
  name: 'invoice-processing',
  displayName: 'Invoice Processing Workflow',
  versionNum: 1,
  mainNodeName: 'invoice-extractor',
  nodes: [
    {
      name: 'invoice-extractor',
      function: { name: 'invoice-extractor', versionNum: 1 }
    }
  ],
  edges: []
}
Workflow(
    id='wf_2def456abc',
    name='invoice-processing',
    display_name='Invoice Processing Workflow',
    version_num=1,
    main_node_name='invoice-extractor',
    nodes=[
        WorkflowNodeResponse(
            name='invoice-extractor',
            function=FunctionVersionIdentifier(name='invoice-extractor', version_num=1),
        )
    ],
    edges=[],
)
bem.Workflow{
    ID:           "wf_2def456abc",
    Name:         "invoice-processing",
    DisplayName:  "Invoice Processing Workflow",
    VersionNum:   1,
    MainNodeName: "invoice-extractor",
    Nodes: []bem.WorkflowNodeResponse{
        {
            Name: "invoice-extractor",
            Function: bem.FunctionVersionIdentifier{
                Name:       "invoice-extractor",
                VersionNum: 1,
            },
        },
    },
    Edges: []bem.WorkflowEdgeResponse{},
}
Bem.Models.Workflows.Workflow {
  ID           = "wf_2def456abc",
  Name         = "invoice-processing",
  DisplayName  = "Invoice Processing Workflow",
  VersionNum   = 1,
  MainNodeName = "invoice-extractor",
  Nodes        = [
    WorkflowNodeResponse {
      Name     = "invoice-extractor",
      Function = FunctionVersionIdentifier { Name = "invoice-extractor", VersionNum = 1 }
    }
  ],
  Edges        = []
}
{
  "workflow": {
    "id": "wf_2def456abc",
    "name": "invoice-processing",
    "displayName": "Invoice Processing Workflow",
    "versionNum": 1,
    "mainNodeName": "invoice-extractor",
    "nodes": [
      {
        "name": "invoice-extractor",
        "function": {
          "name": "invoice-extractor",
          "versionNum": 1
        }
      }
    ],
    "edges": []
  }
}

Step 5: Call the workflow synchronously

Pass wait=true (query param for JSON, form field for multipart) to block until the call finishes and return the completed result in the same response. The endpoint waits up to 30 seconds — longer-running calls still return a pending call object that you can poll or subscribe to.

JSON body with base64-encoded file:

curl -X POST "https://api.bem.ai/v3/workflows/invoice-processing/call?wait=true" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "callReferenceID": "invoice-001",
    "input": {
      "singleFile": {
        "inputType": "pdf",
        "inputContent": "'"$(base64 -i invoice.pdf)"'"
      }
    }
  }'

Or, upload the file as multipart form data:

curl -X POST "https://api.bem.ai/v3/workflows/invoice-processing/call" \
  -H "x-api-key: $BEM_API_KEY" \
  -F "wait=true" \
  -F "callReferenceID=invoice-001" \
  -F "file=@invoice.pdf"
import fs from "node:fs";
import Bem from "bem-ai-sdk";

const client = new Bem();

const inputContent = fs.readFileSync("invoice.pdf").toString("base64");

const { call } = await client.workflows.call("invoice-processing", {
  wait: true,
  callReferenceID: "invoice-001",
  input: {
    singleFile: {
      inputType: "pdf",
      inputContent,
    },
  },
});

console.log(call?.status);
console.log(call?.outputs);
import base64
from bem import Bem

client = Bem()

with open("invoice.pdf", "rb") as f:
    input_content = base64.b64encode(f.read()).decode()

response = client.workflows.call(
    "invoice-processing",
    wait=True,
    call_reference_id="invoice-001",
    input={
        "single_file": {
            "input_type": "pdf",
            "input_content": input_content,
        }
    },
)

print(response.call.status)
print(response.call.outputs)
package main

import (
    "context"
    "encoding/base64"
    "fmt"
    "os"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    data, err := os.ReadFile("invoice.pdf")
    if err != nil {
        panic(err)
    }
    encoded := base64.StdEncoding.EncodeToString(data)

    resp, err := client.Workflows.Call(context.TODO(), "invoice-processing", bem.WorkflowCallParams{
        Wait:            bem.Bool(true),
        CallReferenceID: bem.String("invoice-001"),
        Input: bem.WorkflowCallParamsInput{
            SingleFile: &bem.WorkflowCallParamsInputSingleFile{
                InputType:    "pdf",
                InputContent: encoded,
            },
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("status=%s outputs=%d\n", resp.Call.Status, len(resp.Call.Outputs))
}
using Bem;
using Bem.Models.Workflows;

BemClient client = new();

var fileBytes = await File.ReadAllBytesAsync("invoice.pdf");
var inputContent = Convert.ToBase64String(fileBytes);

var response = await client.Workflows.Call("invoice-processing", new WorkflowCallParams
{
    Wait            = true,
    CallReferenceID = "invoice-001",
    Input = new Input
    {
        SingleFile = new SingleFile
        {
            InputType    = SingleFileInputType.Pdf,
            InputContent = inputContent,
        },
    },
});

Console.WriteLine($"status={response.Call.Status} outputs={response.Call.Outputs.Count}");

The CLI's @path/to/file syntax reads and base64-encodes the file for you.

bem workflows call \
  --workflow-name invoice-processing \
  --call-reference-id invoice-001 \
  --input.single-file '{"inputContent": "@invoice.pdf", "inputType": "pdf"}' \
  --wait

Important: --wait is a boolean flag. Use --wait or --wait=true — not --wait true with a space.

Response when the call finishes in time (HTTP 200):

{
  "call": {
    "callID": "wc_2ghi789def",
    "status": "completed",
    "workflowName": "invoice-processing",
    "workflowVersionNum": 1,
    "callReferenceID": "invoice-001",
    "createdAt": "2024-01-15T10:30:00Z",
    "finishedAt": "2024-01-15T10:30:12Z",
    "outputs": [
      {
        "eventID": "evt_3AlB5PRZFCwAxrwHziH9PQkbkav",
        "eventType": "transform",
        "transformedContent": {
          "invoiceNumber": "INV-2024-0042",
          "invoiceDate": "2024-01-15",
          "dueDate": "2024-02-15",
          "vendor": {
            "name": "Acme Supplies Inc.",
            "address": "123 Business Ave, Suite 100, San Francisco, CA 94107"
          },
          "lineItems": [
            { "description": "Widget A - Premium", "quantity": 10, "unitPrice": 25.0, "amount": 250.0 },
            { "description": "Widget B - Standard", "quantity": 5,  "unitPrice": 15.0, "amount":  75.0 }
          ],
          "subtotal": 325.0,
          "taxAmount": 29.25,
          "totalAmount": 354.25
        }
      }
    ],
    "errors": [],
    "url": "/v3/calls/wc_2ghi789def",
    "traceUrl": "/v3/calls/wc_2ghi789def/trace"
  }
}
{
  callID: 'wc_2ghi789def',
  status: 'completed',
  workflowName: 'invoice-processing',
  workflowVersionNum: 1,
  callReferenceID: 'invoice-001',
  createdAt: '2024-01-15T10:30:00Z',
  finishedAt: '2024-01-15T10:30:12Z',
  outputs: [
    {
      eventID: 'evt_3AlB5PRZFCwAxrwHziH9PQkbkav',
      eventType: 'transform',
      transformedContent: {
        invoiceNumber: 'INV-2024-0042',
        invoiceDate: '2024-01-15',
        dueDate: '2024-02-15',
        vendor: { name: 'Acme Supplies Inc.', address: '123 Business Ave, Suite 100, San Francisco, CA 94107' },
        lineItems: [
          { description: 'Widget A - Premium', quantity: 10, unitPrice: 25, amount: 250 },
          { description: 'Widget B - Standard', quantity: 5,  unitPrice: 15, amount:  75 }
        ],
        subtotal: 325,
        taxAmount: 29.25,
        totalAmount: 354.25
      }
    }
  ],
  errors: [],
  url: '/v3/calls/wc_2ghi789def',
  traceUrl: '/v3/calls/wc_2ghi789def/trace'
}
CallV3(
    call_id='wc_2ghi789def',
    status='completed',
    workflow_name='invoice-processing',
    workflow_version_num=1,
    call_reference_id='invoice-001',
    created_at=datetime.datetime(2024, 1, 15, 10, 30, tzinfo=tzutc()),
    finished_at=datetime.datetime(2024, 1, 15, 10, 30, 12, tzinfo=tzutc()),
    outputs=[
        Event(
            event_id='evt_3AlB5PRZFCwAxrwHziH9PQkbkav',
            event_type='transform',
            transformed_content={
                'invoiceNumber': 'INV-2024-0042',
                'invoiceDate': '2024-01-15',
                'vendor': {'name': 'Acme Supplies Inc.', 'address': '...'},
                'lineItems': [...],
                'totalAmount': 354.25,
            },
        )
    ],
    errors=[],
    url='/v3/calls/wc_2ghi789def',
    trace_url='/v3/calls/wc_2ghi789def/trace',
)
bem.CallV3{
    CallID:             "wc_2ghi789def",
    Status:             "completed",
    WorkflowName:       "invoice-processing",
    WorkflowVersionNum: 1,
    CallReferenceID:    "invoice-001",
    CreatedAt:          time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
    FinishedAt:         time.Date(2024, 1, 15, 10, 30, 12, 0, time.UTC),
    Outputs: []bem.Event{
        {
            EventID:   "evt_3AlB5PRZFCwAxrwHziH9PQkbkav",
            EventType: "transform",
            TransformedContent: map[string]any{
                "invoiceNumber": "INV-2024-0042",
                "invoiceDate":   "2024-01-15",
                "dueDate":       "2024-02-15",
                "vendor": map[string]any{
                    "name":    "Acme Supplies Inc.",
                    "address": "123 Business Ave, Suite 100, San Francisco, CA 94107",
                },
                "lineItems": []any{
                    map[string]any{"description": "Widget A - Premium", "quantity": 10, "unitPrice": 25.0, "amount": 250.0},
                    map[string]any{"description": "Widget B - Standard", "quantity": 5, "unitPrice": 15.0, "amount": 75.0},
                },
                "subtotal":    325.0,
                "taxAmount":   29.25,
                "totalAmount": 354.25,
            },
        },
    },
    Errors:   []bem.ErrorEvent{},
    URL:      "/v3/calls/wc_2ghi789def",
    TraceURL: "/v3/calls/wc_2ghi789def/trace",
}
Bem.Models.Calls.CallV3 {
  CallID             = "wc_2ghi789def",
  Status             = "completed",
  WorkflowName       = "invoice-processing",
  WorkflowVersionNum = 1,
  CallReferenceID    = "invoice-001",
  CreatedAt          = 2024-01-15T10:30:00Z,
  FinishedAt         = 2024-01-15T10:30:12Z,
  Outputs = [
    Event {
      EventID   = "evt_3AlB5PRZFCwAxrwHziH9PQkbkav",
      EventType = "transform",
      TransformedContent = {
        "invoiceNumber": "INV-2024-0042",
        "invoiceDate":   "2024-01-15",
        "dueDate":       "2024-02-15",
        "vendor":        { "name": "Acme Supplies Inc.", "address": "..." },
        "lineItems":     [ /* ... */ ],
        "subtotal":      325.0,
        "taxAmount":     29.25,
        "totalAmount":   354.25
      }
    }
  ],
  Errors   = [],
  Url      = "/v3/calls/wc_2ghi789def",
  TraceUrl = "/v3/calls/wc_2ghi789def/trace"
}
{
  "call": {
    "callID": "wc_2ghi789def",
    "status": "completed",
    "workflowName": "invoice-processing",
    "workflowVersionNum": 1,
    "callReferenceID": "invoice-001",
    "createdAt": "2024-01-15T10:30:00Z",
    "finishedAt": "2024-01-15T10:30:12Z",
    "outputs": [
      {
        "eventID": "evt_3AlB5PRZFCwAxrwHziH9PQkbkav",
        "eventType": "extract",
        "transformedContent": {
          "invoiceNumber": "INV-2024-0042",
          "invoiceDate": "2024-01-15",
          "dueDate": "2024-02-15",
          "vendor": {
            "name": "Acme Supplies Inc.",
            "address": "123 Business Ave, Suite 100, San Francisco, CA 94107"
          },
          "lineItems": [
            { "description": "Widget A - Premium", "quantity": 10, "unitPrice": 25.0, "amount": 250.0 },
            { "description": "Widget B - Standard", "quantity": 5,  "unitPrice": 15.0, "amount":  75.0 }
          ],
          "subtotal": 325.0,
          "taxAmount": 29.25,
          "totalAmount": 354.25
        }
      }
    ],
    "errors": [],
    "url": "/v3/calls/wc_2ghi789def",
    "traceUrl": "/v3/calls/wc_2ghi789def/trace"
  }
}

Tip: pipe through jq '.call.outputs[0].transformedContent' to extract just the extracted fields.

Reading the response

The extracted data lives at call.outputs[0].transformedContent — note the plural outputs[] (it's always an array, even with one terminal node) and the top-level transformedContent (the payload is on the event itself, not nested under a transformation field). For workflows where the terminal node is an Enrich function, the payload is at enrichedContent instead; the field name is keyed off eventType.

# With jq
curl ... | jq '.call.outputs[0].transformedContent'
const { call } = await client.workflows.call("invoice-processing", { wait: true, /* ... */ });
const data = call?.outputs?.[0]?.transformedContent;
console.log(data?.invoiceNumber, data?.totalAmount);
response = client.workflows.call("invoice-processing", wait=True, ...)
data = response.call.outputs[0].transformed_content
print(data["invoiceNumber"], data["totalAmount"])
resp, err := client.Workflows.Call(ctx, "invoice-processing", bem.WorkflowCallParams{Wait: bem.Bool(true) /* ... */})
if err != nil { panic(err) }
data := resp.Call.Outputs[0].TransformedContent
fmt.Printf("%+v\n", data)
var response = await client.Workflows.Call("invoice-processing", new WorkflowCallParams { Wait = true /* ... */ });
var data = response.Call.Outputs[0].TransformedContent;
Console.WriteLine(data);
bem workflows call --workflow-name invoice-processing --wait \
  --call-reference-id invoice-001 \
  --input.single-file '{"inputContent": "@invoice.pdf", "inputType": "pdf"}' \
  --transform 'call.outputs.0.transformedContent'

The --transform flag uses GJSON syntax so the CLI prints just the extracted JSON.

For the full per-event-type field map, the outputs[] array semantics, and accessor patterns when a workflow has multiple terminal nodes (branching/splitting), see Reading workflow call outputs.

If the call takes longer than 30 seconds, the endpoint returns HTTP 202 with status: "pending" or "running". When that happens, either poll GET /v3/calls/{callID} or configure a webhook subscription to receive the result asynchronously.

Expanding your workflow

Workflows are easy to iterate on — you can keep layering functions into the DAG as your pipeline grows. To show how that works, let's add an Enrich step that matches each extracted line-item description to a SKU in a product catalog.

We'll:

  1. Create a collection to hold the catalog
  2. Populate it with a handful of products
  3. Create an enrich function that does a semantic lookup against the collection
  4. Wire the enrich function into invoice-processing so it runs after invoice-extractor

Step 6a: Create a product catalog collection

curl -X POST https://api.bem.ai/v3/collections \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{"collectionName": "product_catalog"}'
import Bem from "bem-ai-sdk";

const client = new Bem();

const collection = await client.collections.create({
  collectionName: "product_catalog",
});

console.log(collection);
from bem import Bem

client = Bem()

collection = client.collections.create(
    collection_name="product_catalog",
)
print(collection)
package main

import (
    "context"
    "fmt"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    collection, err := client.Collections.New(context.TODO(), bem.CollectionNewParams{
        CollectionName: "product_catalog",
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", collection)
}
using Bem;
using Bem.Models.Collections;

BemClient client = new();

var collection = await client.Collections.Create(new CollectionCreateParams
{
    CollectionName = "product_catalog",
});

Console.WriteLine(collection);
bem collections create --collection-name product_catalog

Response:

{
  "collectionID": "cl_2N6gH8ZKCmvb6BnFcGqhKJ98VzP",
  "collectionName": "product_catalog",
  "itemCount": 0,
  "createdAt": "2026-04-22T15:30:00Z",
  "updatedAt": "2026-04-22T15:30:00Z"
}
{
  collectionID: 'cl_2N6gH8ZKCmvb6BnFcGqhKJ98VzP',
  collectionName: 'product_catalog',
  itemCount: 0,
  createdAt: '2026-04-22T15:30:00Z',
  updatedAt: '2026-04-22T15:30:00Z'
}
CollectionCreateResponse(
    collection_id='cl_2N6gH8ZKCmvb6BnFcGqhKJ98VzP',
    collection_name='product_catalog',
    item_count=0,
    created_at=datetime.datetime(2026, 4, 22, 15, 30, tzinfo=tzutc()),
    updated_at=datetime.datetime(2026, 4, 22, 15, 30, tzinfo=tzutc()),
)
&bem.CollectionNewResponse{
    CollectionID:   "cl_2N6gH8ZKCmvb6BnFcGqhKJ98VzP",
    CollectionName: "product_catalog",
    ItemCount:      0,
    CreatedAt:      time.Date(2026, 4, 22, 15, 30, 0, 0, time.UTC),
    UpdatedAt:      time.Date(2026, 4, 22, 15, 30, 0, 0, time.UTC),
}
Bem.Models.Collections.CollectionCreateResponse {
  CollectionID   = "cl_2N6gH8ZKCmvb6BnFcGqhKJ98VzP",
  CollectionName = "product_catalog",
  ItemCount      = 0,
  CreatedAt      = 2026-04-22T15:30:00Z,
  UpdatedAt      = 2026-04-22T15:30:00Z
}
{
  "collectionID": "cl_2N6gH8ZKCmvb6BnFcGqhKJ98VzP",
  "collectionName": "product_catalog",
  "itemCount": 0,
  "createdAt": "2026-04-22T15:30:00Z",
  "updatedAt": "2026-04-22T15:30:00Z"
}

Step 6b: Add products to the collection

Each item's data can be a plain string or a JSON object. Objects let you retrieve structured fields (SKU, unit cost, category) when a match is found.

curl -X POST https://api.bem.ai/v3/collections/items \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "collectionName": "product_catalog",
    "items": [
      { "data": { "sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50 } },
      { "data": { "sku": "WGT-B-STD-002", "name": "Widget B - Standard Model",  "category": "Widgets", "unitCost":  9.25 } },
      { "data": { "sku": "WGT-C-ECO-003", "name": "Widget C - Economy Line",    "category": "Widgets", "unitCost":  5.00 } }
    ]
  }'
import Bem from "bem-ai-sdk";

const client = new Bem();

const response = await client.collections.items.add({
  collectionName: "product_catalog",
  items: [
    { data: { sku: "WGT-A-PRM-001", name: "Widget A - Premium Edition", category: "Widgets", unitCost: 18.50 } },
    { data: { sku: "WGT-B-STD-002", name: "Widget B - Standard Model",  category: "Widgets", unitCost:  9.25 } },
    { data: { sku: "WGT-C-ECO-003", name: "Widget C - Economy Line",    category: "Widgets", unitCost:  5.00 } },
  ],
});

console.log(response);
from bem import Bem

client = Bem()

response = client.collections.items.add(
    collection_name="product_catalog",
    items=[
        {"data": {"sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50}},
        {"data": {"sku": "WGT-B-STD-002", "name": "Widget B - Standard Model",  "category": "Widgets", "unitCost":  9.25}},
        {"data": {"sku": "WGT-C-ECO-003", "name": "Widget C - Economy Line",    "category": "Widgets", "unitCost":  5.00}},
    ],
)
print(response)
package main

import (
    "context"
    "fmt"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    response, err := client.Collections.Items.Add(context.TODO(), bem.CollectionItemAddParams{
        CollectionName: "product_catalog",
        Items: []bem.CollectionItemAddParamsItem{
            {Data: map[string]any{"sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50}},
            {Data: map[string]any{"sku": "WGT-B-STD-002", "name": "Widget B - Standard Model", "category": "Widgets", "unitCost": 9.25}},
            {Data: map[string]any{"sku": "WGT-C-ECO-003", "name": "Widget C - Economy Line", "category": "Widgets", "unitCost": 5.00}},
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", response)
}
using System.Text.Json;
using Bem;
using Bem.Models.Collections.Items;

BemClient client = new();

var response = await client.Collections.Items.Add(new ItemAddParams
{
    CollectionName = "product_catalog",
    Items = new List<ItemAddParamsItem>
    {
        new(new ItemAddParamsItemData(JsonSerializer.Deserialize<JsonElement>("""
            {"sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50}
            """))),
        new(new ItemAddParamsItemData(JsonSerializer.Deserialize<JsonElement>("""
            {"sku": "WGT-B-STD-002", "name": "Widget B - Standard Model",  "category": "Widgets", "unitCost":  9.25}
            """))),
        new(new ItemAddParamsItemData(JsonSerializer.Deserialize<JsonElement>("""
            {"sku": "WGT-C-ECO-003", "name": "Widget C - Economy Line",    "category": "Widgets", "unitCost":  5.00}
            """))),
    },
});

Console.WriteLine(response);
bem collections:items add \
  --collection-name product_catalog \
  --item '{data: {sku: WGT-A-PRM-001, name: "Widget A - Premium Edition", category: Widgets, unitCost: 18.50}}' \
  --item '{data: {sku: WGT-B-STD-002, name: "Widget B - Standard Model",  category: Widgets, unitCost:  9.25}}' \
  --item '{data: {sku: WGT-C-ECO-003, name: "Widget C - Economy Line",    category: Widgets, unitCost:  5.00}}'

Response:

{
  "status": "pending",
  "message": "Collection items are being processed asynchronously",
  "eventID": "evt_2N6gH8ZKCmvb6BnFcGqhKJ98VzP"
}
{
  status: 'pending',
  message: 'Collection items are being processed asynchronously',
  eventID: 'evt_2N6gH8ZKCmvb6BnFcGqhKJ98VzP'
}
ItemAddResponse(
    status='pending',
    message='Collection items are being processed asynchronously',
    event_id='evt_2N6gH8ZKCmvb6BnFcGqhKJ98VzP',
)
&bem.CollectionItemAddResponse{
    Status:  "pending",
    Message: "Collection items are being processed asynchronously",
    EventID: "evt_2N6gH8ZKCmvb6BnFcGqhKJ98VzP",
}
Bem.Models.Collections.Items.ItemAddResponse {
  Status  = "pending",
  Message = "Collection items are being processed asynchronously",
  EventID = "evt_2N6gH8ZKCmvb6BnFcGqhKJ98VzP"
}
{
  "status": "pending",
  "message": "Collection items are being processed asynchronously",
  "eventID": "evt_2N6gH8ZKCmvb6BnFcGqhKJ98VzP"
}

Items are embedded asynchronously. For a catalog this small it finishes within a few seconds — call client.collections.items.retrieve({ collectionName: "product_catalog" }) (or the equivalent in your SDK) if you want to confirm before continuing.

Step 6c: Create an enrich function

The enrich function pulls every line item description out of the upstream payload, runs a semantic search against product_catalog, and injects the top match at matchedProducts.

curl -X POST https://api.bem.ai/v3/functions \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "functionName": "sku-matcher",
    "type": "enrich",
    "displayName": "SKU Matcher",
    "tags": ["products", "sku-lookup"],
    "config": {
      "steps": [
        {
          "sourceField": "lineItems[*].description",
          "collectionName": "product_catalog",
          "targetField": "matchedProducts",
          "topK": 1,
          "searchMode": "semantic"
        }
      ]
    }
  }'
import Bem from "bem-ai-sdk";

const client = new Bem();

const { function: fn } = await client.functions.create({
  functionName: "sku-matcher",
  type: "enrich",
  displayName: "SKU Matcher",
  tags: ["products", "sku-lookup"],
  config: {
    steps: [
      {
        sourceField: "lineItems[*].description",
        collectionName: "product_catalog",
        targetField: "matchedProducts",
        topK: 1,
        searchMode: "semantic",
      },
    ],
  },
});

console.log(fn);
from bem import Bem

client = Bem()

response = client.functions.create(
    function_name="sku-matcher",
    type="enrich",
    display_name="SKU Matcher",
    tags=["products", "sku-lookup"],
    config={
        "steps": [
            {
                "sourceField": "lineItems[*].description",
                "collectionName": "product_catalog",
                "targetField": "matchedProducts",
                "topK": 1,
                "searchMode": "semantic",
            }
        ],
    },
)
print(response.function)
package main

import (
    "context"
    "fmt"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    resp, err := client.Functions.New(context.TODO(), bem.FunctionNewParams{
        CreateFunction: bem.CreateFunctionUnionParam{
            OfEnrich: &bem.CreateFunctionEnrichParam{
                FunctionName: "sku-matcher",
                DisplayName:  bem.String("SKU Matcher"),
                Tags:         []string{"products", "sku-lookup"},
                Config: bem.EnrichConfigParam{
                    Steps: []bem.EnrichStepParam{
                        {
                            SourceField:    "lineItems[*].description",
                            CollectionName: "product_catalog",
                            TargetField:    "matchedProducts",
                            TopK:           bem.Int(1),
                            SearchMode:     bem.EnrichStepSearchModeSemantic,
                        },
                    },
                },
            },
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", resp.Function)
}
using Bem;
using Bem.Models.Functions;

BemClient client = new();

var response = await client.Functions.Create(new FunctionCreateParams
{
    CreateFunction = new Enrich
    {
        FunctionName = "sku-matcher",
        DisplayName  = "SKU Matcher",
        Tags         = new List<string> { "products", "sku-lookup" },
        Config = new EnrichConfig
        {
            Steps = new List<EnrichStep>
            {
                new EnrichStep
                {
                    SourceField    = "lineItems[*].description",
                    CollectionName = "product_catalog",
                    TargetField    = "matchedProducts",
                    TopK           = 1,
                    SearchMode     = EnrichStepSearchMode.Semantic,
                },
            },
        },
    },
});

Console.WriteLine(response.Function);
bem functions create \
  --function-name sku-matcher \
  --type enrich \
  --display-name "SKU Matcher" \
  --tag products --tag sku-lookup \
  --config '{steps: [{sourceField: "lineItems[*].description", collectionName: product_catalog, targetField: matchedProducts, topK: 1, searchMode: semantic}]}'

Response:

{
  "function": {
    "functionID": "fn_7ghi789jkl",
    "functionName": "sku-matcher",
    "displayName": "SKU Matcher",
    "type": "enrich",
    "currentVersionNum": 1
  }
}
{
  functionID: 'fn_7ghi789jkl',
  functionName: 'sku-matcher',
  displayName: 'SKU Matcher',
  type: 'enrich',
  currentVersionNum: 1
}
Function(
    function_id='fn_7ghi789jkl',
    function_name='sku-matcher',
    display_name='SKU Matcher',
    type='enrich',
    current_version_num=1,
)
bem.Function{
    FunctionID:        "fn_7ghi789jkl",
    FunctionName:      "sku-matcher",
    DisplayName:       "SKU Matcher",
    Type:              "enrich",
    CurrentVersionNum: 1,
}
Bem.Models.Functions.Function {
  FunctionID        = "fn_7ghi789jkl",
  FunctionName      = "sku-matcher",
  DisplayName       = "SKU Matcher",
  Type              = "enrich",
  CurrentVersionNum = 1
}
{
  "function": {
    "functionID": "fn_7ghi789jkl",
    "functionName": "sku-matcher",
    "displayName": "SKU Matcher",
    "type": "enrich",
    "currentVersionNum": 1
  }
}

Step 6d: Chain the enrich function into the workflow

PATCH /v3/workflows/{workflowName} takes the full desired node/edge topology and creates a new workflow version. Add sku-matcher as a second node, then connect invoice-extractor → sku-matcher with an edge.

curl -X PATCH https://api.bem.ai/v3/workflows/invoice-processing \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "mainNodeName": "invoice-extractor",
    "nodes": [
      { "name": "invoice-extractor", "function": { "name": "invoice-extractor" } },
      { "name": "sku-matcher",       "function": { "name": "sku-matcher" } }
    ],
    "edges": [
      { "sourceNodeName": "invoice-extractor", "destinationNodeName": "sku-matcher" }
    ]
  }'
import Bem from "bem-ai-sdk";

const client = new Bem();

const { workflow } = await client.workflows.update("invoice-processing", {
  mainNodeName: "invoice-extractor",
  nodes: [
    { name: "invoice-extractor", function: { name: "invoice-extractor" } },
    { name: "sku-matcher",       function: { name: "sku-matcher" } },
  ],
  edges: [
    { sourceNodeName: "invoice-extractor", destinationNodeName: "sku-matcher" },
  ],
});

console.log(workflow);
from bem import Bem

client = Bem()

response = client.workflows.update(
    "invoice-processing",
    main_node_name="invoice-extractor",
    nodes=[
        {"name": "invoice-extractor", "function": {"name": "invoice-extractor"}},
        {"name": "sku-matcher",       "function": {"name": "sku-matcher"}},
    ],
    edges=[
        {"source_node_name": "invoice-extractor", "destination_node_name": "sku-matcher"},
    ],
)
print(response.workflow)
package main

import (
    "context"
    "fmt"

    bem "github.com/bem-team/bem-go-sdk"
)

func main() {
    client := bem.NewClient()

    resp, err := client.Workflows.Update(context.TODO(), "invoice-processing", bem.WorkflowUpdateParams{
        MainNodeName: bem.String("invoice-extractor"),
        Nodes: []bem.WorkflowUpdateParamsNode{
            {
                Name:     bem.String("invoice-extractor"),
                Function: bem.FunctionVersionIdentifierParam{Name: bem.String("invoice-extractor")},
            },
            {
                Name:     bem.String("sku-matcher"),
                Function: bem.FunctionVersionIdentifierParam{Name: bem.String("sku-matcher")},
            },
        },
        Edges: []bem.WorkflowUpdateParamsEdge{
            {
                SourceNodeName:      "invoice-extractor",
                DestinationNodeName: "sku-matcher",
            },
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", resp.Workflow)
}
using Bem;
using Bem.Models.Workflows;

BemClient client = new();

var response = await client.Workflows.Update("invoice-processing", new WorkflowUpdateParams
{
    MainNodeName = "invoice-extractor",
    Nodes = new List<WorkflowUpdateParamsNode>
    {
        new WorkflowUpdateParamsNode { Function = new FunctionVersionIdentifier { Name = "invoice-extractor" }, Name = "invoice-extractor" },
        new WorkflowUpdateParamsNode { Function = new FunctionVersionIdentifier { Name = "sku-matcher" },       Name = "sku-matcher" },
    },
    Edges = new List<WorkflowUpdateParamsEdge>
    {
        new WorkflowUpdateParamsEdge { SourceNodeName = "invoice-extractor", DestinationNodeName = "sku-matcher" },
    },
});

Console.WriteLine(response.Workflow);
bem workflows update \
  --workflow-name invoice-processing \
  --main-node-name invoice-extractor \
  --node '{name: invoice-extractor, function: {name: invoice-extractor}}' \
  --node '{name: sku-matcher, function: {name: sku-matcher}}' \
  --edge '{sourceNodeName: invoice-extractor, destinationNodeName: sku-matcher}'

Response:

{
  "workflow": {
    "id": "wf_2def456abc",
    "name": "invoice-processing",
    "displayName": "Invoice Processing Workflow",
    "versionNum": 2,
    "mainNodeName": "invoice-extractor",
    "nodes": [
      { "name": "invoice-extractor", "function": { "name": "invoice-extractor", "versionNum": 1 } },
      { "name": "sku-matcher",       "function": { "name": "sku-matcher",       "versionNum": 1 } }
    ],
    "edges": [
      { "sourceNodeName": "invoice-extractor", "destinationNodeName": "sku-matcher" }
    ]
  }
}
{
  id: 'wf_2def456abc',
  name: 'invoice-processing',
  displayName: 'Invoice Processing Workflow',
  versionNum: 2,
  mainNodeName: 'invoice-extractor',
  nodes: [
    { name: 'invoice-extractor', function: { name: 'invoice-extractor', versionNum: 1 } },
    { name: 'sku-matcher',       function: { name: 'sku-matcher',       versionNum: 1 } }
  ],
  edges: [
    { sourceNodeName: 'invoice-extractor', destinationNodeName: 'sku-matcher' }
  ]
}
Workflow(
    id='wf_2def456abc',
    name='invoice-processing',
    display_name='Invoice Processing Workflow',
    version_num=2,
    main_node_name='invoice-extractor',
    nodes=[
        WorkflowNodeResponse(name='invoice-extractor', function=FunctionVersionIdentifier(name='invoice-extractor', version_num=1)),
        WorkflowNodeResponse(name='sku-matcher',       function=FunctionVersionIdentifier(name='sku-matcher',       version_num=1)),
    ],
    edges=[
        WorkflowEdgeResponse(source_node_name='invoice-extractor', destination_node_name='sku-matcher'),
    ],
)
bem.Workflow{
    ID:           "wf_2def456abc",
    Name:         "invoice-processing",
    DisplayName:  "Invoice Processing Workflow",
    VersionNum:   2,
    MainNodeName: "invoice-extractor",
    Nodes: []bem.WorkflowNodeResponse{
        {Name: "invoice-extractor", Function: bem.FunctionVersionIdentifier{Name: "invoice-extractor", VersionNum: 1}},
        {Name: "sku-matcher",       Function: bem.FunctionVersionIdentifier{Name: "sku-matcher",       VersionNum: 1}},
    },
    Edges: []bem.WorkflowEdgeResponse{
        {SourceNodeName: "invoice-extractor", DestinationNodeName: "sku-matcher"},
    },
}
Bem.Models.Workflows.Workflow {
  ID           = "wf_2def456abc",
  Name         = "invoice-processing",
  DisplayName  = "Invoice Processing Workflow",
  VersionNum   = 2,
  MainNodeName = "invoice-extractor",
  Nodes        = [
    WorkflowNodeResponse { Name = "invoice-extractor", Function = FunctionVersionIdentifier { Name = "invoice-extractor", VersionNum = 1 } },
    WorkflowNodeResponse { Name = "sku-matcher",       Function = FunctionVersionIdentifier { Name = "sku-matcher",       VersionNum = 1 } }
  ],
  Edges        = [
    WorkflowEdgeResponse { SourceNodeName = "invoice-extractor", DestinationNodeName = "sku-matcher" }
  ]
}
{
  "workflow": {
    "id": "wf_2def456abc",
    "name": "invoice-processing",
    "displayName": "Invoice Processing Workflow",
    "versionNum": 2,
    "mainNodeName": "invoice-extractor",
    "nodes": [
      { "name": "invoice-extractor", "function": { "name": "invoice-extractor", "versionNum": 1 } },
      { "name": "sku-matcher",       "function": { "name": "sku-matcher",       "versionNum": 1 } }
    ],
    "edges": [
      { "sourceNodeName": "invoice-extractor", "destinationNodeName": "sku-matcher" }
    ]
  }
}

Call the enriched workflow

Invoke the workflow the same way as Step 5 — no client changes required. Because sku-matcher is now the terminal node, the call's outputs array contains an enrich event whose enrichedContent carries the extracted invoice plus a matchedProducts array sorted by relevance:

{
  "call": {
    "callID": "wc_8mno012pqr",
    "status": "completed",
    "workflowName": "invoice-processing",
    "workflowVersionNum": 2,
    "outputs": [
      {
        "eventID": "evt_5abc678def",
        "eventType": "enrich",
        "functionName": "sku-matcher",
        "enrichedContent": {
          "invoiceNumber": "INV-2024-0042",
          "lineItems": [
            { "description": "Widget A - Premium", "quantity": 10, "unitPrice": 25.0, "amount": 250.0 },
            { "description": "Widget B - Standard", "quantity": 5,  "unitPrice": 15.0, "amount":  75.0 }
          ],
          "matchedProducts": [
            {
              "data": { "sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50 },
              "cosineDistance": 0.0823
            },
            {
              "data": { "sku": "WGT-B-STD-002", "name": "Widget B - Standard Model", "category": "Widgets", "unitCost": 9.25 },
              "cosineDistance": 0.1104
            }
          ]
        }
      }
    ],
    "errors": [],
    "url": "/v3/calls/wc_8mno012pqr",
    "traceUrl": "/v3/calls/wc_8mno012pqr/trace"
  }
}
{
  callID: 'wc_8mno012pqr',
  status: 'completed',
  workflowName: 'invoice-processing',
  workflowVersionNum: 2,
  outputs: [
    {
      eventID: 'evt_5abc678def',
      eventType: 'enrich',
      functionName: 'sku-matcher',
      enrichedContent: {
        invoiceNumber: 'INV-2024-0042',
        lineItems: [
          { description: 'Widget A - Premium',  quantity: 10, unitPrice: 25, amount: 250 },
          { description: 'Widget B - Standard', quantity:  5, unitPrice: 15, amount:  75 }
        ],
        matchedProducts: [
          { data: { sku: 'WGT-A-PRM-001', name: 'Widget A - Premium Edition', category: 'Widgets', unitCost: 18.5 }, cosineDistance: 0.0823 },
          { data: { sku: 'WGT-B-STD-002', name: 'Widget B - Standard Model',  category: 'Widgets', unitCost:  9.25 }, cosineDistance: 0.1104 }
        ]
      }
    }
  ],
  errors: [],
  url: '/v3/calls/wc_8mno012pqr',
  traceUrl: '/v3/calls/wc_8mno012pqr/trace'
}
CallV3(
    call_id='wc_8mno012pqr',
    status='completed',
    workflow_name='invoice-processing',
    workflow_version_num=2,
    outputs=[
        EnrichEvent(
            event_id='evt_5abc678def',
            event_type='enrich',
            function_name='sku-matcher',
            enriched_content={
                'invoiceNumber': 'INV-2024-0042',
                'lineItems': [...],
                'matchedProducts': [
                    {'data': {'sku': 'WGT-A-PRM-001', 'name': 'Widget A - Premium Edition', ...}, 'cosineDistance': 0.0823},
                    {'data': {'sku': 'WGT-B-STD-002', 'name': 'Widget B - Standard Model',  ...}, 'cosineDistance': 0.1104},
                ],
            },
        )
    ],
    errors=[],
    url='/v3/calls/wc_8mno012pqr',
    trace_url='/v3/calls/wc_8mno012pqr/trace',
)
bem.CallV3{
    CallID:             "wc_8mno012pqr",
    Status:             "completed",
    WorkflowName:       "invoice-processing",
    WorkflowVersionNum: 2,
    Outputs: []bem.Event{
        {
            EventID:      "evt_5abc678def",
            EventType:    "enrich",
            FunctionName: "sku-matcher",
            EnrichedContent: map[string]any{
                "invoiceNumber": "INV-2024-0042",
                "lineItems":     []any{ /* ... */ },
                "matchedProducts": []any{
                    map[string]any{
                        "data":           map[string]any{"sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50},
                        "cosineDistance": 0.0823,
                    },
                    map[string]any{
                        "data":           map[string]any{"sku": "WGT-B-STD-002", "name": "Widget B - Standard Model", "category": "Widgets", "unitCost": 9.25},
                        "cosineDistance": 0.1104,
                    },
                },
            },
        },
    },
    Errors:   []bem.ErrorEvent{},
    URL:      "/v3/calls/wc_8mno012pqr",
    TraceURL: "/v3/calls/wc_8mno012pqr/trace",
}
Bem.Models.Calls.CallV3 {
  CallID             = "wc_8mno012pqr",
  Status             = "completed",
  WorkflowName       = "invoice-processing",
  WorkflowVersionNum = 2,
  Outputs = [
    EnrichEvent {
      EventID      = "evt_5abc678def",
      EventType    = "enrich",
      FunctionName = "sku-matcher",
      EnrichedContent = {
        "invoiceNumber": "INV-2024-0042",
        "lineItems":     [ /* ... */ ],
        "matchedProducts": [
          { "data": { "sku": "WGT-A-PRM-001", ... }, "cosineDistance": 0.0823 },
          { "data": { "sku": "WGT-B-STD-002", ... }, "cosineDistance": 0.1104 }
        ]
      }
    }
  ],
  Errors   = [],
  Url      = "/v3/calls/wc_8mno012pqr",
  TraceUrl = "/v3/calls/wc_8mno012pqr/trace"
}
{
  "call": {
    "callID": "wc_8mno012pqr",
    "status": "completed",
    "workflowName": "invoice-processing",
    "workflowVersionNum": 2,
    "outputs": [
      {
        "eventID": "evt_5abc678def",
        "eventType": "enrich",
        "functionName": "sku-matcher",
        "enrichedContent": {
          "invoiceNumber": "INV-2024-0042",
          "lineItems": [
            { "description": "Widget A - Premium", "quantity": 10, "unitPrice": 25.0, "amount": 250.0 },
            { "description": "Widget B - Standard", "quantity": 5,  "unitPrice": 15.0, "amount":  75.0 }
          ],
          "matchedProducts": [
            {
              "data": { "sku": "WGT-A-PRM-001", "name": "Widget A - Premium Edition", "category": "Widgets", "unitCost": 18.50 },
              "cosineDistance": 0.0823
            },
            {
              "data": { "sku": "WGT-B-STD-002", "name": "Widget B - Standard Model", "category": "Widgets", "unitCost": 9.25 },
              "cosineDistance": 0.1104
            }
          ]
        }
      }
    ],
    "errors": [],
    "url": "/v3/calls/wc_8mno012pqr",
    "traceUrl": "/v3/calls/wc_8mno012pqr/trace"
  }
}

The intermediate extract event is still available via GET /v3/calls/{callID}/trace if you need to inspect each function call in the DAG.

Webhook delivery (optional)

For production pipelines that may exceed the 30-second wait window, subscribe a webhook so bem pushes results to your server the moment processing completes.

curl -X POST https://api.bem.ai/v1-alpha/subscriptions \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BEM_API_KEY" \
  -d '{
    "name": "invoice-results",
    "type": "transform",
    "functionName": "invoice-extractor",
    "webhookURL": "https://your-server.com/webhooks/bem"
  }'

Verify incoming requests with the bem-signature header — see Webhook Authentication for the HMAC-SHA256 recipe.

Next Steps

On this page