Skip to content

Flow steps and step keys

A step is a ## Step: section. Steps run in the order they appear in the file, one at a time, and each one decides what the next one sees.

## Step: forward-order
effects: [http_egress]
endpoint: https://orders.example.com/ingest
method: POST

Names and order

A step name follows ## Step: on the same line. Names are for you and for the logs: they appear in error responses, in log events and in the audit trail, so make them describe what the step does rather than where it sits.

Execution order is file order, always. There is no dependency graph and no reordering. A validate step placed after a network call validates after the call has already happened — put your checks first.

What a step does with the message

One message travels through the flow. Each step reads it as ctx.input and, unless it says otherwise, replaces it with what it produces.

The step has What the next step sees
A fence (ntd, xslt, …) The value the fence produced
An effect and no fence The result of the effect — for a request, the response
An effect and a fence The fence output is sent; the response replaces the message
A condition fence that is false Nothing. Execution stops and the message is dropped
A validate fence that fails Nothing. The request is rejected

A step with http_egress and no fence sends the current message as-is. That is the common shape for a forwarding step.

Keeping a copy of the message

A call replaces the message with its response, which loses the request. save_body and restore_body are how you keep it.

save_body: <variable> snapshot ctx.input into a variable, before anything else runs
restore_body: <variable> overwrite ctx.input from that variable, after the body is evaluated

save_body happens before effects and before the fence is evaluated, so it captures the message as the step received it.

---
flowmarkdown_version: "0.1"
flow: forward-and-acknowledge
tenant: acme
effects: [http_egress]
---
## Step: keep-request
save_body: original
```ntd
{ "orderId": "{{ $.orderId }}" }
```
## Step: send
effects: [http_egress]
endpoint: https://orders.example.com/ingest
method: POST
## Step: answer-caller
restore_body: original
```ntd
{
"accepted": true,
"orderId": "{{ $.orderId }}",
"downstreamStatus": "{{ ctx.original.orderId }}"
}
```

The variable is an ordinary entry in ctx.vars, readable as ctx.<name>. Pick names that will not collide with anything else the flow writes.

Two names are refused at compile time — so nexus validate and nexus deploy both reject them: correlated and correlated_late. The platform resolves those from typed context fields before the variable map, so a save_body: correlated would store a value no read can ever reach, and a restore_body: correlated would read a slot nothing can fill — silently, with no error at run time. The same refusal applies to a secret_read step whose key: (or, without key:, whose sanitised step name) is one of the two. Already-published artifacts are not revisited; the refusal applies when you publish.

Step key reference

Any step

Key Type Meaning
effects list The effects this step is permitted to perform. Enforced before the step runs
save_body variable name Snapshot ctx.input into that variable
restore_body variable name Replace ctx.input from that variable

effects: on the step is the one that is enforced. The flow-level list documents the flow’s capabilities; the step-level list is the permission. A step attempting an effect it did not declare fails before any socket is opened.

HTTP calls

Used with effects: [http_egress]. See Outbound HTTP calls.

Key Default Meaning
endpoint — Target URL. Accepts {{ }} interpolation
method POST HTTP method
content_type application/json Request content type; also selects body encoding
accept — Accept header
bearer_token — Literal, or a {{ ctx.<var> }} template — the name is checked at publication. A credential is read with secret_read; an environment variable written in the text is refused at publication, see What a flow may read
headers — Inline-JSON map of extra request headers; every value is a {{ ... }} template, checked at publication
connect_timeout_ms — Budget for establishing the connection
read_timeout_ms — Budget for reading the response
max_message_bytes flow, then platform Ceiling on the body sent from this step, and on the response read back
error_body_max_bytes 4096 How much of a non-2xx response body is kept in ctx.HTTP_RESPONSE_BODY
tls_ca_cert — Path to a PEM file of CA certificates; replaces the built-in roots for this step
tls_client_cert — Path to a PEM client certificate, for mutual TLS
tls_client_key — Path to its PEM key. Required together with tls_client_cert
breaker_failure_threshold 3 Refused connections before the destination is skipped
breaker_window_ms 60000 How long a refused connection stays relevant
breaker_cooldown_ms 30000 How long a skipped destination rests before a probe
breaker_cooldown_factor 2.0 Multiplier on the cooldown after a failed probe
breaker_cooldown_max_ms 300000 Ceiling for the escalating cooldown

SOAP

Added to an http_egress step. See SOAP and WSDL.

Key Meaning
soap_version 1.1 or 1.2. Presence is what turns the call into a SOAP call
soap_action SOAPAction header value
soap_operation Operation name used when wrapping the body

gRPC calls

Used with effects: [grpc_egress]. See Outbound gRPC calls.

Key Default Meaning
proto — Descriptor name
proto_version latest Contract version
service — Fully-qualified service name
method — Method name. Not the HTTP method:
endpoint — Host and port; https:// selects TLS
deadline_ms 30000 Total budget for the call
connect_timeout_ms 10000 Handshake budget
max_message_bytes the flow’s ceiling Message ceiling for this step alone
max_recv_bytes = max_message_bytes Largest single response message
max_send_bytes = max_message_bytes Largest single request message
max_stream_messages 10000 Most messages accepted from a stream
max_stream_bytes = max(max_message_bytes, max_recv_bytes) Most stream bytes in total
tls_ca_cert — Path to a PEM CA certificate
tls_client_cert — Path to a PEM client certificate, for mutual TLS
tls_client_key — Path to its PEM key. Required together with tls_client_cert
bearer_token — A {{ ctx.<var> }} template or a literal; grpc_egress sends it as authorization: Bearer <value>. An environment variable written in the text is refused at publication, see What a flow may read
metadata — Inline JSON object of the metadata entries grpc_egress sends, each value a string or an array of strings, every string a template
breaker_failure_threshold 3 Failed handshakes before the endpoint is skipped
breaker_window_ms 60000 How long a failed handshake stays relevant
breaker_cooldown_ms 30000 How long a skipped endpoint rests before a probe
breaker_cooldown_factor 2.0 Multiplier on the cooldown after a failed probe
breaker_cooldown_max_ms 300000 Ceiling for the escalating cooldown

method: means two different things depending on the effect. On an HTTP step it is the verb; on a gRPC step it is the method name from the contract.

Publishing to a queue

Used with effects: [queue_publish]. See Queues, retries and the DLQ.

Key Meaning
queue Required. The flow whose queue receives the message. A step without it is refused at compile time, and so is one whose value breaks the rule for a name that becomes a path

There is deliberately no tenant key: the message goes to the publishing flow’s own tenant. A key able to redirect it elsewhere would be a tenant boundary crossing configured in a flow file.

Secrets and tokens

See Secrets and OAuth2.

Key Effect Meaning
key secret_read Secret name. Defaults to the step name if omitted
token_url oauth2_token Token endpoint. Accepts {{ }} interpolation
client_id oauth2_token Literal or {{ ctx.<var> }}
client_secret oauth2_token Literal or {{ ctx.<var> }}
scope oauth2_token Requested scope. Accepts {{ }} interpolation
grant_type oauth2_token Defaults to client_credentials. Accepts {{ }} interpolation
tls_ca_cert oauth2_token Path to a PEM file of CA certificates for the token endpoint; replaces the built-in roots
tls_client_cert oauth2_token Path to a PEM client certificate, presented to the token endpoint; with it, client_secret may be omitted
tls_client_key oauth2_token Path to its PEM key. Required together with tls_client_cert

token_url, client_id, client_secret, scope and grant_type, like bearer_token, are templates: a {{ ctx.<var> }} inside them is checked at publication, and the bare ctx.<var> spelling is refused with the fix named. The three TLS keys are file paths, read at every call. See Secrets and OAuth2.

Opening a correlation

Valid on any step. See Correlation.

Key Meaning
correlation_open The key this exchange will be answered under. A template, like dedup_key
correlation_state Inline JSON object of name → template: what gets rehydrated when the answer arrives. {} is legal. A value that evaluates to an object or an array holding an XML element is refused when the step runs, and the error names the field; carry one element’s text with string(descendants($, 'Item')[0]), or the texts of all with join(descendants($, 'Item'), '|').
correlation_deadline_ms How long to wait. Required — a wait with no end is a leak, not a policy
correlation_timeout_flow Optional. The flow to run when the deadline passes

The first three go together on the same step. Missing any of them is a compile error, and so is a companion key without correlation_open: — which is what catches the key you misspelled.

The response to the caller

Valid on any step — a flow that succeeds shapes its answer with the same keys a ## Fault: section uses. Collected from the steps that actually ran, in execution order. See Handling failures.

Key Meaning
response_status HTTP status returned to the caller. Literal, or an NTD template such as "{{ ctx.HTTP_SC }}". A literal outside 100–599 is a compile error
response_header_<name> A response header, literal value. Last writer wins per name
response_header_remove Names to take back out of the accumulated set: [x-internal, x-ephemeral]

response_header_<name> converts underscores to hyphens and capitalises each segment, so response_header_retry_after: 60 sends Retry-After: 60 and response_header_content_type: application/problem+json sends Content-Type.

fault_status: and fault_header_<name>: are the deprecated spellings of the first two. They still work, but only inside a ## Fault: section — writing one on a regular step is refused at publication, by name. Both are also refused on any step of a library flow, wherever it is reached from.

An unknown key is an error

A key nothing will read is refused, with the closest match offered:

Terminal window
$ nexus validate search.flow.md
search.flow.md:14:1: error: step 'search': unknown key `max_stream_messsages` — did you mean `max_stream_messages`?
error: 1 validation error(s)

The check is per effect, not against one flat list. A key that is real but belongs to an effect the step did not declare says so, because the fix is a missing effects: entry rather than a spelling correction:

Terminal window
send.flow.md:18:1: error: step 'send': unknown key `key` — `key` is read by `secret_read` — add it to this step's `effects:`

A key of another protocol is refused the same way: deadline_ms is a gRPC key, and on an HTTP step nothing reads it:

Terminal window
send.flow.md:18:1: error: step 'send': unknown key `deadline_ms` — `deadline_ms` is read by `grpc_egress` — add it to this step's `effects:`

It runs in the same pass as the other name checks, so nexus validate catches it, not only nexus compile and nexus deploy.