Skip to content

Steps

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 requests

Used with effects: [http_egress]. See HTTP requests.

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, ${VAR} from the environment, or a {{ ctx.<var> }} template — the name is checked at publication
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

SOAP

Added to an http_egress step. See SOAP.

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 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
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

All five, 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. 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
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.

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
error: step 'search': unknown key `max_stream_messsages` — did you mean `max_stream_messages`?

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
error: step 'send': unknown key `key` — `key` is read by `secret_read` — add it to this step's `effects:`

And a key belonging to another effect entirely is refused too — tls_ca_cert on an HTTP step is as dead as an invented one:

Terminal window
error: step 'send': unknown key `tls_ca_cert` — accepted here: accept, bearer_token, connect_timeout_ms, …

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