What is a Flow
A Flow is a JSON-defined execution graph that orchestrates a series of operations (called operators) to process data. Each operator performs a specific task and connects to the next operator, forming a chain of operations.
- Flow: A collection of interconnected operators forming an execution graph.
- Operator: A node in the graph that performs a specific operation (transform data, make HTTP calls, branch logic, etc.).
- Global Context: A JSON object that holds all data during flow execution, accessible to all operators.
- JSONata: An expression language used for querying and transforming JSON data within operators.
- Input data is placed in the global context (default key:
request). - Execution starts from the operator specified in
startOperatorId. - Each operator processes data, modifies the global context, and indicates the next operator.
- Execution continues until a termination operator (
ReturnorFail) is reached. - The final result is extracted and returned.
Flow Structure
A flow definition in JSON consists of the following top-level properties:
{
"flowId": "unique-flow-identifier",
"startOperatorId": "first-operator-id",
"inputDataKey": "request",
"operators": [
// Array of operator definitions
]
}
| Property | Required | Type | Default | Description |
|---|---|---|---|---|
| flowId | Yes | String | - | Unique identifier for the flow |
| startOperatorId | Yes | String | - | ID of the operator where execution begins |
| inputDataKey | No | String | request | Key under which input data is stored in the global context |
| operators | Yes | Array | - | Array of operator definitions |
- flowId: Must be unique across your application. Used for identifying and loading flows.
- startOperatorId: Must match the
idof one of the operators in theoperatorsarray. - inputDataKey: Allows you to customize how input data is accessed. For
example, if set to
payload, usepayload.fieldinstead ofrequest.fieldin expressions.
Flow Execution with Metadata
Metadata is an optional JSON object that can be passed when executing a flow. It provides contextual information that can be accessed by any operator in the flow through JSONata expressions. Metadata is separate from the input data and is typically used for configuration values, request tracking, user context, feature flags, and environment-specific parameters.
- Passed at execution time in the flow execution request.
- Available throughout the flow; all operators can access metadata via JSONata expressions.
- Immutable during flow execution.
- Optional; flows can work without metadata if not required.
Flow Execution Request Structure:
{
"metadata": {
"key1": "value1",
"key2": "value2"
},
"input": [
{
"data_field1": "data_value1",
"data_field2": "data_value2"
},
{
"data_field1": "data_value3",
"data_field2": "data_value4"
}
]
}
| Property | Required | Type | Description |
|---|---|---|---|
| metadata | No | JSON Object | Optional metadata accessible to all operators |
| input | Yes | Array of JSON Objects | Array of input data objects to be processed |
- The
metadatafield is optional; you can omit it if not needed. - The
inputfield is required and contains an array of input records. - Each input record is processed independently by the flow.
- All input records in the batch share the same metadata.
Accessing Metadata in Flows: Metadata can be accessed in JSONata expressions
using the metadata prefix, e.g.
metadata.geocodeUrl, metadata.requestId.
- Dataset selection
- Request tracking
- Feature flags
- Configuration parameters
Metadata vs Input Data:
| Aspect | Metadata | Input Data |
|---|---|---|
| Purpose | Configuration, context, parameters | Actual data to be processed |
| Scope | Shared across all input records in a batch | Individual to each record |
| Mutability | Immutable during flow execution | Transformed and enriched during flow |
| Access | metadata.fieldName |
request.fieldName (or custom
inputDataKey) |
| Required | Optional | Required |
| Use Cases | URLs, flags, settings, tracking IDs | Business data to process |
Example: Complete Flow with Metadata
Execution Request:
{
"metadata": {
"datasetType": "Premium",
"requestId": "req-98765",
"enableCaching": true
},
"input": [
{
"addressLines": ["123 Main St"],
"city": "New York",
"country": "USA"
}
]
}
Flow Definition:
{
"flowId": "geocode-with-metadata",
"startOperatorId": "validate_country",
"inputDataKey": "request",
"operators": [
{
"id": "validate_country",
"type": "Predicate",
"description": "Check if country is supported",
"condition": "$exists(request.country) and request.country != ''",
"successNode": "prepare_request",
"failureNode": "country_missing_error"
},
{
"id": "prepare_request",
"type": "Transform",
"description": "Build geocode request with metadata parameters",
"operation": "{'address': request, 'dataset': metadata.datasetType, 'requestId': metadata.requestId}",
"key": "geocode_request",
"nextNode": "call_geocode"
},
{
"id": "call_geocode",
"type": "Post",
"description": "Call geocoding API using metadata URL",
"url": "https://api.precisely.com/v1/geocode",
"headers": "{'Content-Type': 'application/json', 'X-Request-ID': metadata.requestId}",
"body": "{'addresses': [request], 'preferences': {'customPreferences': {'DATA_OPTION': metadata.datasetType}}}",
"requiredElements": "responses[0]",
"key": "geocode_result",
"nextNode": "check_cache_flag"
},
{
"id": "check_cache_flag",
"type": "Predicate",
"description": "Check if caching should be enabled",
"condition": "metadata.enableCaching = true",
"successNode": "return_with_cache_info",
"failureNode": "return_result"
},
{
"id": "return_with_cache_info",
"type": "Return",
"resultExpression": "{'result': geocode_result, 'cached': true, 'requestId': metadata.requestId}"
},
{
"id": "return_result",
"type": "Return",
"resultExpression": "{'result': geocode_result, 'requestId': metadata.requestId}"
},
{
"id": "country_missing_error",
"type": "Fail",
"errorCode": 400,
"status": "VALIDATION_ERROR",
"errorMessage": "Country field is required"
}
]
}
- Use metadata for configuration values that may vary between environments.
- Include tracking identifiers (requestId, correlationId) for debugging.
- Use metadata for feature flags and conditional behavior.
- Keep metadata structure consistent across requests.
- Document expected metadata fields in flow documentation.
Handling Optional Metadata: Always check for existence using
$exists(metadata.field) and provide defaults as needed.
$exists(metadata.apiUrl) ? metadata.apiUrl : 'https://default-api.example.com'
$exists(metadata.usePremium) and metadata.usePremium = true
metadata.timeout ? metadata.timeout : 5000
Available Operators
The Multipass SDK provides several built-in operators for common operations. Each operator has a specific purpose and set of properties.
- Simple Operators: Perform operations and proceed to a single next node (Transform, Get, Post)
- Conditional Operators: Branch execution based on conditions (Predicate)
- Termination Operators: End flow execution and return results (Return, Fail)
Transform Operator
Purpose: Transform data using JSONata expressions and store the result in the global context.
Use Cases: Extract fields, perform calculations, restructure JSON, combine fields.
| Property | Required | Type | Description |
|---|---|---|---|
| id | Yes | String | Unique identifier for this operator |
| type | Yes | String | Must be "Transform" |
| operation | Yes | String | JSONata expression that transforms data |
| key | Yes | String | Key where the transformation result will be stored |
| nextNode | Yes | String | ID of the next operator to execute |
| description | No | String | Human-readable description |
JSON Example:
{
"id": "transform_city",
"type": "Transform",
"description": "Convert city name to uppercase and trim spaces",
"operation": "$uppercase($trim(request.city))",
"key": "normalized_city",
"nextNode": "next_operator"
}
Common JSONata Operations:
"operation": "request.city"
"operation": "$uppercase($trim(request.city))"
"operation": "request.age >= 18 ? 'adult' : 'minor'"
"operation": "$join(request.addressLines, ', ')"
"operation": "{'fullName': request.firstName request.lastName, 'age': request.age}"
"operation": "request.price * 1.1"
Get Operator
Purpose: Perform HTTP GET requests to external APIs and store the response in the global context.
Use Cases: Fetch data from REST APIs, retrieve configuration, call external services.
| Property | Required | Type | Description |
|---|---|---|---|
| id | Yes | String | Unique identifier for this operator |
| type | Yes | String | Must be "Get" |
| url | Yes | String | The URL endpoint for the GET request |
| key | Yes | String | Key where the HTTP response will be stored |
| nextNode | Yes | String | ID of the next operator to execute |
| description | No | String | Human-readable description |
| urlParams | No | Array | List of URL query parameters |
| headers | No | String | JSONata expression for HTTP headers |
| requiredElements | No | String | JSONata expression to extract specific fields from the response |
| failOnError | No | Boolean | If true, flow fails on HTTP errors (default: true) |
Automatic Bearer Token Injection: The Multipass service automatically injects
the bearer token from the Authorization header into your input data as
request.bearer_token.
JSON Example:
{
"id": "get_user_data",
"type": "Get",
"description": "Fetch user profile from API",
"url": "https://api.example.com/users",
"urlParams": [
{"key": "id", "value": "request.userId"},
{"key": "include", "value": "'profile,settings'"}
],
"headers": "{'Authorization': 'Bearer ' request.token, 'Accept': 'application/json'}",
"requiredElements": "data.profile",
"key": "user_profile",
"nextNode": "process_user"
}
Post Operator
Purpose: Perform HTTP POST requests to external APIs with a request body and store the response.
Use Cases: Submit data to APIs, create or update resources, authenticate, send data for processing.
| Property | Required | Type | Description |
|---|---|---|---|
| id | Yes | String | Unique identifier for this operator |
| type | Yes | String | Must be "Post" |
| url | Yes | String | The URL endpoint for the POST request |
| body | Yes | String | JSONata expression for the request body |
| key | Yes | String | Key where the HTTP response will be stored |
| nextNode | Yes | String | ID of the next operator to execute |
| description | No | String | Human-readable description |
| urlParams | No | Array | List of URL query parameters |
| headers | No | String | JSONata expression for HTTP headers |
| requiredElements | No | String | JSONata expression to extract specific fields from the response |
| failOnError | No | Boolean | If true, flow fails on HTTP errors (default: true) |
JSON Example:
{
"id": "create_user",
"type": "Post",
"description": "Create a new user account",
"url": "https://api.example.com/users",
"body": "{'email': request.email, 'name': request.name, 'age': request.age}",
"headers": "{'Content-Type': 'application/json', 'Authorization': 'Bearer 'auth_token}",
"requiredElements": "data.userId",
"key": "created_user",
"nextNode": "return_success"
}
Predicate Operator
Purpose: Implement conditional branching logic based on boolean expressions.
Use Cases: Validate input data, route execution, implement business rules, error handling.
| Property | Required | Types | Description |
|---|---|---|---|
| id | Yes | String | Unique identifier for this operator |
| type | Yes | String | Must be "Predicate" |
| condition | Yes | String | JSONata expression that evaluates to boolean |
| successNode | Yes | String | ID of the operator to execute if condition is true |
| failureNode | Yes | String | ID of the operator to execute if condition is false |
| description | No | String | Human-readable description |
JSON Example:
{
"id": "check_age",
"type": "Predicate",
"description": "Check if user is an adult",
"condition": "request.age >= 18",
"successNode": "adult_flow",
"failureNode": "minor_flow"
}
Return Operator
Purpose: Terminate the flow successfully and return data to the caller.
Use Cases: Return processed results, success responses, end flow execution.
| Property | Required | Type | Description |
|---|---|---|---|
| id | Yes | String | Unique identifier for this operator |
| type | Yes | String | Must be "Return" |
| resultExpression | Yes | String | JSONata expression specifying what data to return |
| description | No | String | Human-readable description |
JSON Example:
{
"id": "return_result",
"type": "Return",
"description": "Return the processed user data",
"resultExpression": "processed_user"
}
Fail Operator
Purpose: Terminate the flow with an error state.
Use Cases: Handle validation failures, report errors, terminate on unrecoverable conditions.
| Property | Required | Type | Description |
|---|---|---|---|
| id | Yes | String | Unique identifier for this operator |
| type | Yes | String | Must be "Fail" |
| description | No | String | Human-readable description |
| errorCode | No | Integer | HTTP-style error code (default: 500) |
| status | No | String | Custom status string |
| errorMessage | No | String | Error message to return |
JSON Example:
{
"id": "validation_failed",
"type": "Fail",
"description": "Input validation failed",
"errorCode": 400,
"status": "VALIDATION_ERROR",
"errorMessage": "Required field 'email' is missing"
}
Error Handling in Flows
Understanding how errors propagate through flows and implementing proper error handling is crucial for building robust workflows.
- HTTP Operators (Get/Post): By default, HTTP errors (4xx, 5xx) will fail the
flow. Use
failOnError: falseto continue execution even on HTTP errors. - Transform Operators: JSONata expression errors will fail the flow.
- Predicate Operators: Condition evaluation errors will fail the flow.
- Fail Operators: Explicitly terminate flow with error.
Error Handling Patterns: See the original guide for detailed JSON examples of error handling, fallback, retry logic, validation chains, and error enrichment.
JSONata Expressions
JSONata is a powerful query and transformation language for JSON data. It's used extensively in Multipass flows.
- String:
$uppercase(),$lowercase(),$trim(),$contains() - Numeric:
$number(),$round(),$sum() - Array:
$count(),$exists(),$append() - Object:
$keys(),$merge() - Date/Time:
$now(),$fromMillis()
Accessing Data in Expressions: Use request.field for input
data, metadata.field for metadata, and $ for the
entire global context.
Expression Validation: Always validate your JSONata expressions before deploying flows. Use the JSONata Exerciser to test expressions with sample data.
Complete Flow Examples
See the original guide for detailed JSON examples of flows for data transformation, API integration, multi-step orchestration, error handling, and metadata usage.
Best Practices
Flow Design: Use descriptive IDs, add descriptions, keep flows focused, plan error handling, avoid circular references.
JSONata Expressions: Test expressions, use readable code, handle nulls, avoid complexity.
Error Handling: Validate input early, provide meaningful error messages, use appropriate codes, document failure scenarios.
Metadata Usage: Use for configuration, tracking, feature flags; keep structure simple; check for existence; do not store sensitive data.
API Integration: Store tokens, use requiredElements, set
headers, handle errors.
Batch Processing: Use metadata for batch-level config, design flows to be stateless, do not access other records' results.
Performance: Minimize HTTP calls, reuse computed values, cache data when possible.
Maintenance: Version flows, document logic, test with various inputs, keep under version control.
Naming Conventions: Use verb_noun for operator IDs, descriptive nouns for keys, kebab-case for flow IDs.
Testing: Test all branches, error paths, with/without metadata, batch processing.
API Usage Summary
Creating a Flow:
POST /v1/multipass/flows/{flowId}
Retrieving a Flow:
GET /v1/multipass/flows/{flowId}
Executing a Flow:
POST /v1/multipass/flows/{flowId}/execute
Updating a Flow:
PUT /v1/multipass/flows/{flowId}
Deleting a Flow:
DELETE /v1/multipass/flows/{flowId}
Listing All Flows:
GET /v1/multipass/flows
Key Points: Authentication required, metadata is optional, batch processing
supported, request tracking via X-Request-ID, always use
application/json, flows are cached, API returns appropriate
status codes.
Quick Reference Card
| Operator | Purpose | Next Node | Key Properties |
|---|---|---|---|
| Transform | Transform data with JSONata | Single (nextNode) |
operation, key |
| Get | HTTP GET request | Single (nextNode) |
url, key |
| Post | HTTP POST request | Single (nextNode) |
url, body,
key |
| Predicate | Conditional branching | Two (successNode,
failureNode) |
condition |
| Return | Successful termination | None (ends flow) | resultExpression |
| Fail | Failed termination | None (ends flow) | errorCode, errorMessage |
Additional Resources
- JSONata Documentation
- JSONata Exerciser
- See
src/test/resources/flows/for example flows.