Other types
ActionDefaults
| Field | Type | Description |
|---|---|---|
expect_status | StatusExpectation (nullable) | Seeds http.expect_status on every action of this interface that does not set its own. |
StatusExpectation
expect_status: — either a single matcher or a list of them.
One of:
- StatusMatcher
- Array<StatusMatcher>
StatusMatcher
One entry in an expect_status / retry.on list.
Accepts an exact code (404), a class (2xx, 4xx, 5xx), any, or — for
retry.on only — a transport class (timeout, connect).
One of:
- number
- string
BetweenRange
An inclusive range for is_between.
| Field | Type | Description |
|---|---|---|
min | number | Required. Lowest accepted value, inclusive. |
max | number | Required. Highest accepted value, inclusive. |
Jitter
How much randomisation to apply to a retry delay.
One of:
- string — Sleep a random duration in
[0, computed]— AWS's "full jitter", the default. - string — Sleep exactly the computed backoff. Predictable, and synchronises clients.
ExitExpectation
expect_exit: — one code, a list of codes, or any.
One of:
- number
- Array<number>
- string — Only
anyis meaningful here; anything else never matches, which is the safe way for a typo to behave.
Idempotency
Idempotency-key configuration for an HTTP action.
RFC 9110 §9.2.2 says a client should not automatically retry a non-idempotent request
"unless it has some means to know that the request semantics are actually idempotent...
or some means to detect that the original request was never applied". An idempotency
key is that means, and is being standardised as the Idempotency-Key header
(draft-ietf-httpapi-idempotency-key-header). Providers differ on the header name —
Stripe uses Idempotency-Key, PayPal PayPal-Request-Id — so it is configurable.
| Field | Type | Description |
|---|---|---|
key | string (nullable) | The key to send. Omit it and the engine generates a UUID per action execution, reused across that execution's retries. Set it to derive the key from your own data… |
header | string (nullable) | Header to carry the key. Defaults to Idempotency-Key. |
Sftp
One SFTP operation against one server.
| Field | Type | Description |
|---|---|---|
host | string | Required. Hostname or address of the SFTP server. |
port | number (nullable) | Port. Defaults to 22. |
user | string | Required. Username to authenticate as. |
password | string (nullable) | Password. Use a|secret::NAME| rather than a literal. |
private_key | string (nullable) | Private key in PEM/OpenSSH form, preferred over a password. Use a|secret::NAME| rather than a literal. |
private_key_passphrase | string (nullable) | Passphrase for private_key, if it has one. |
host_key | string (nullable) | The server's host key fingerprint, as ssh-keygen -lf prints it: SHA256:kW8xDm2YuGv9NsLzalFWnMJ3j28XTnQJzw7/5GkE9+w. Required unless accept_any_host_key is set. SSH's… |
accept_any_host_key | boolean (nullable) | Connect without verifying the server's identity. Defaults to false. Only for a server you reach over a network you already trust end to end. Setting it means credentials in… |
timeout | string (nullable) | How long to wait to connect and how long the session may sit idle, e.g. 30s. Defaults to 30 seconds. |
operation | string | Required. What to do: list, get, put, delete, rename or mkdir. list returns an array of {name, path, size, is_dir, modified}, where modified is epoch seconds — so it… |
path | string | Required. The remote path the operation applies to. |
to | string (nullable) | Destination path, for rename. |
content | string (nullable) | The bytes to write, for put. |
encoding | string (nullable) | text (default) or base64. Use base64 for anything that is not UTF-8 — a get of a binary file as text is an error rather than mangled output. |
OnlyNew
Filter a list down to the records this config has not seen before.
Takes the action's input (a list, or a single record treated as a list of one),
works out each record's identity, and passes on only those whose identity is new —
recording them as it goes, in a single state write for the whole page.
| Field | Type | Description |
|---|---|---|
id | string (nullable) | A jq expression selecting each record's identity. Defaults to .id. Whatever it returns is compared as text, so a number, a string or a composite all work. Use a composite… Example ↓ |
key | string | Required. The dedupe set to remember ids in. Two actions sharing a key share their memory. |
namespace | string (nullable) | Optional namespace for the set. Defaults to the config name. |
ttl | string (nullable) | How long an id stays remembered, e.g. 30d. Without one it is remembered until evicted by the size cap (the oldest 5000 ids are kept). Set this to slightly longer than the… |
skip_when_empty | boolean (nullable) | Skip this action — and anything that depends on it — when nothing is new. Defaults to true. A poll that finds nothing new almost always wants to do nothing, and a skip is how… |
Field examples
id
A jq expression selecting each record's identity. Defaults to .id.
Whatever it returns is compared as text, so a number, a string or a composite all work. Use a composite when no single field is unique:
id: '.user_id + "/" + .updated_at'
A record whose id comes back null or missing is passed through rather than silently dropped — losing records because a field was misspelled is the failure this is meant to prevent, not cause.
Discover
Discover the live members of a service — container endpoints, pods, replicas — and hand them to the rest of the pipeline as an array.
The point is to stop hard-coding a list of addresses that changes every time
something scales, restarts or reschedules. A discover action returns one
entry per member, and any later action fans out across them with lookup::
actions:
- name: Members
discover:
kubernetes:
label_selector: app=api
port_name: http
- name: PollAll
lookup: a|Members|
lookup_partition: true # -> { succeeded: [...], failed: [...] }
actions:
- name: Health
http:
url: a|body::url|/healthz
Every backend returns the SAME item shape, so a config written against one orchestrator moves to another by changing only the backend block:
{
"name": "api-7d9f8b6c5d-x2ktp",
"address": "10.42.1.3",
"port": 8080,
"url": "http://10.42.1.3:8080",
"source": "kubernetes",
"ready": true,
"namespace": "default",
"node": "worker-1",
"image": "example/api:1.4.0",
"labels": { "app": "api" }
}
Fields a backend cannot know are omitted rather than faked — DNS returns
addresses only, so it has no labels, image or ready. Filtering on a field
a backend does not provide therefore matches nothing, which is why
label_selector belongs to the backends that actually have labels.
| Field | Type | Description |
|---|---|---|
dns | DiscoverDns (nullable) | Resolve a DNS name to every address behind it. The portable option: no API access, no credentials, no RBAC, nothing to mount. In Kubernetes point it at a headless Service… |
kubernetes | DiscoverKubernetes (nullable) | Query the Kubernetes API for pods. The richest option: real label selectors, plus namespace, node, readiness and image per pod. Requires a ServiceAccount with list on pods,… |
docker | DiscoverDocker (nullable) | Query the Docker Engine API for containers. For container hosts that are not Kubernetes: plain Docker, Compose, Swarm. Requires access to the Docker socket, which is equivalent… |
static | Array<string> (nullable) | An explicit list of host:port entries. Not discovery — the escape hatch for environments with none of the above, and what tests pin against. Keeping it in the same shape… Example ↓ |
include | DiscoverFilter (nullable) | Keep only members matching these patterns. Applied before exclude. |
exclude | DiscoverFilter (nullable) | Drop members matching these patterns. Exclusion wins over inclusion, deliberately: when the two disagree the safe reading of the operator's intent is "do not touch this one". |
port | DiscoverPort (nullable) | Port to use when the backend does not supply one (DNS always, Kubernetes when neither port nor port_name is set). Without it those members are returned with no port and no… |
scheme | string (nullable) | Scheme used to build each member's url. Defaults to http. |
cache_ttl_secs | number (nullable) | How long to reuse a result before querying again, in seconds. Defaults to 10; 0 disables caching. This matters more than it looks. Without it, a discovery action on a… |
fail_when_empty | boolean (nullable) | Fail the action when discovery returns nothing. Defaults to false, so an empty result is an empty loop rather than an error — a service legitimately scaled to zero is not a… |
Field examples
static
An explicit list of host:port entries.
Not discovery — the escape hatch for environments with none of the above, and what tests pin against. Keeping it in the same shape means a config can start static and move to real discovery without touching the actions that consume it.
Accepts either a YAML sequence or a single comma-separated string, so the
whole list can come from ONE variable — which is how a member list is
usually configured, and how AIRPIPE__WS_MESH_PEERS already works:
static: "10.0.0.1:8080, 10.0.0.2:8080" # one variable
static: a|ap_var::MEMBERS| # ...including from a variable
static: ["10.0.0.1:8080", "10.0.0.2:8080"] # or a sequence
Empty and null entries are dropped rather than rejected. Configs are
re-parsed AFTER interpolation, so an entry whose variable resolves to
nothing (a|ap_var::MEMBER_2->default()|) arrives here as a bare -,
i.e. YAML null. Failing there would break the config only in the
environment where that variable happened to be unset — in production,
having passed every test.
DiscoverDns
DNS-based discovery.
| Field | Type | Description |
|---|---|---|
name | string | Required. The name to resolve — airpipe-mesh, api.default.svc.cluster.local, or a Compose service name. A bare name takes the action's port; name:port is honoured as written. |
exclude_self | string (nullable) | Drop this node's own address from the results. Useful when a service discovers its peers and should not call itself. Needs the address to exclude, e.g. a|env::POD_IP|. |
DiscoverKubernetes
Kubernetes API discovery.
| Field | Type | Description |
|---|---|---|
namespace | string (nullable) | Namespace to search. Defaults to the agent's own namespace, read from the ServiceAccount token mount — so the common case needs no configuration and stays inside the… |
label_selector | string (nullable) | Standard Kubernetes label selector, e.g. app=api,tier!=canary. Passed through to the API server, so the same syntax as kubectl -l. |
field_selector | string (nullable) | Standard field selector, e.g. status.phase=Running. |
port_name | string (nullable) | Take the port from a named container port (http, metrics). Preferred over a hard-coded number: the name survives a port change. |
ready_only | boolean (nullable) | Return only pods whose containers are all ready. Defaults to true — an unready pod is one that has told you not to send it traffic. |
DiscoverDocker
Docker Engine API discovery.
| Field | Type | Description |
|---|---|---|
labels | Map<string, string> (nullable) | Container labels to match, e.g. com.docker.compose.service: api. All listed labels must match. |
network | string (nullable) | Docker network whose address should be reported for each container. With several networks attached and no choice made, the first is used. |
socket | string (nullable) | Path or URL of the Docker Engine socket. Defaults to /var/run/docker.sock. |
DiscoverFilter
Regex patterns matched against a member's fields. An unset field matches everything; a field the backend does not populate never matches.
| Field | Type | Description |
|---|---|---|
name | string (nullable) | Regex against the member name (pod or container name). |
image | string (nullable) | Regex against the full image reference, including registry and tag. |
namespace | string (nullable) | Regex against the namespace (Kubernetes only). |
DiscoverPort
A port: a literal number, or a marker that resolves to one.
Every other field of a discover block interpolates, because they are
strings. A bare u16 could not, so port: a|ap_var::SERVICE_PORT| did not
merely fail to resolve — the marker was still marker TEXT when serde reached
it, and the agent died at load with invalid type: string ... expected u16.
A config that names its port the same way it names everything else should
work, so the string form is accepted and parsed after interpolation.
One of:
- number —
port: 8080 - string —
port: a|ap_var::SERVICE_PORT->default(8080)|, or a quoted"8080". Still a marker at parse time; resolved once interpolation has run.
WaitForCallback
Wait for something outside to call back before continuing.
Needs a durable run store, the same as a long delay: managed has one, self-hosted needs
AIRPIPE__DATABASE_URL. Without one the wait is refused rather than becoming an in-process
pause that a restart would lose.
| Field | Type | Description |
|---|---|---|
token | string | Required. The secret that identifies this wait. Anyone holding it can resume the run, so it must be unguessable — mint it with a|uuid| in an earlier action rather than using something… |
timeout | string | Required. How long to wait before giving up, as a duration ("7d", "2h"). Required, and there is no unbounded option: a wait nobody ever answers would hold a row forever. When it… |
EmailAttachment
One file attached to an outgoing email.
| Field | Type | Description |
|---|---|---|
filename | string | Required. Filename shown to the recipient. |
content | string | Required. The file contents. base64 by default — pair it with the b64_encode post-transform, which is also what makes binary files (PDF, images) work. Use encoding: utf8 to attach… |
content_type | string (nullable) | MIME type, e.g. text/csv, application/pdf. Defaults to application/octet-stream. |
encoding | string (nullable) | How content is encoded: base64 (default) or utf8. |
WsPublish
Publish a payload to realtime WebSocket channels (server push / fan-out). Every socket subscribed to any listed channel — on this node AND every peer node — receives the payload. Fire-and-forget: it does not wait for delivery.
Example
- name: Broadcast
ws_publish:
channels: a|body::rooms| # a string, an array, or an a|...| marker
data: a|OrderAction| # defaults to a|body| when omitted
| Field | Type | Description |
|---|---|---|
channels | any | Required. Channel key(s) to publish to: a string, an array of strings, or an a|... marker resolving to either. |
data | any | The payload pushed to subscribers. An a|... marker or inline JSON. Defaults to the request/message body when omitted. |
retain | any | Store the payload as the channel's RETAINED value, so a subscriber that connects (or reconnects) later immediately receives the current value on subscribe. Defaults to false.… |
MqttPublish
A mqtt_publish action publishes a message from a pipeline to one or more MQTT
topics. Every subscriber to a listed topic — on this node AND every peer node (via
the realtime mesh) — receives the payload. This is the pipeline→topic direction
(the inbound topic→pipeline direction is the ingest consumer). Fire-and-forget: it
does not wait for broker acknowledgement.
Example
- name: Push
mqtt_publish:
topics: a|body::topic| # a string, an array, or an a|...| marker
data: a|Reading| # defaults to a|body| when omitted
qos: 1 # 0 (default) | 1 | 2
retain: false # default false
| Field | Type | Description |
|---|---|---|
topics | any | Required. Topic(s) to publish to: a string, an array of strings, or an a|... marker resolving to either. |
data | any | The payload published to subscribers. An a|... marker or inline JSON. A JSON string publishes as raw text; any other value publishes as its JSON encoding. Defaults to the… |
qos | number (nullable) | MQTT quality of service for the published message: 0 (at most once, default), 1 (at least once), or 2 (exactly once). |
retain | any | Whether the broker retains this message as the topic's last-known-good value, delivered to future subscribers on subscribe. Defaults to false. Accepts a literal boolean OR an… |
TransformAggregate
Reduce a JSON array to a summary (sum / avg / min / max / count), optionally grouped.
| Field | Type | Description |
|---|---|---|
over | string (nullable) | JSONPath to the array to aggregate. Omit to aggregate the whole data when it is itself an array. |
key | string (nullable) | Output field to hold the summary. Omit to replace the data with the summary. |
sum | Array<string> (nullable) | Fields to sum. |
avg | Array<string> (nullable) | Fields to average. |
min | Array<string> (nullable) | Fields to take the minimum of. |
max | Array<string> (nullable) | Fields to take the maximum of. |
count | boolean (nullable) | Include a row count in the summary (default true when no other op is given). |
group_by | Array<string> (nullable) | Group rows by these fields; the summary is computed per group under groups. |
HmacSign
Parameters for the hmac post-transform (signing).
Example
hmac:
key: signature
data: a|BuildPayload::body|
secret: a|Subscriber::secret|
algorithm: sha256
encoding: hex
| Field | Type | Description |
|---|---|---|
key | string | Required. Output key the computed digest is written to. |
data | string | Required. The message to sign. Supports interpolation. |
secret | string | Required. The signing secret. Supports interpolation — use a|ap_var::NAME| or a per-recipient secret read from a prior action. |
algorithm | string (nullable) | Hash algorithm: sha1, sha256 (default), or sha512. |
encoding | string (nullable) | Digest encoding: hex (default) or base64. |
InterfaceContract
What an interface accepts or returns, as a contract: a shape and an example of it.
One type for both directions so the two sides stay symmetric, and named for what an interface does rather than for HTTP: interfaces are also MQTT subscriptions, WebSocket channels, MCP tools and cron schedules, and only one of those has a "request".
Before this, the inbound side had a derived schema plus a flat request_example and
the outbound side had only a flat response_example — two different shapes for the
same idea, and 299 hand-written examples across the marketplace packs that nothing
validated against reality.
Both fields are optional: the schema is derived when it can be, and the example is best produced by promoting a recorded run rather than typing one out.
| Field | Type | Description |
|---|---|---|
schema | any | JSON Schema for this side of the call. Derived from assertions when omitted. |
example | any | A representative payload, shown in the generated docs. |
OnError
A handler that runs when a run of this config fails.
It runs after the response has been decided and is never awaited, so it cannot add latency to the request that failed, and its own failure cannot change what the caller received. A handler that fails is logged and dropped.
| Field | Type | Description |
|---|---|---|
interface | string | Required. Name of an interface in this config to run. It receives a JSON body describing what failed: Example ↓ |
status | Array<number> (nullable) | Which status codes count as a failure worth handling. Defaults to any non-2xx. Example ↓ |
max_per_minute | number (nullable) | The most handler runs to start in any one minute, per config. Defaults to 12. This is a guard, not a tuning knob. An error handler that fires per request turns an outage into a… Example ↓ |
Field examples
interface
Name of an interface in this config to run. It receives a JSON body describing what failed:
{
"config": "orders-api",
"interface": "CreateOrder",
"http_code": 500,
"run_uuid": "0199...",
"at": "2026-08-22T04:15:00Z",
"data": { }
}
so a handler reads a|body->interface|, a|body->http_code| and the rest.
status
Which status codes count as a failure worth handling. Defaults to any non-2xx.
Example
status: [500, 502, 503, 504]
max_per_minute
The most handler runs to start in any one minute, per config. Defaults to 12.
This is a guard, not a tuning knob. An error handler that fires per request turns an outage into a second outage: every failing request would start another run, against a dependency that is already unhealthy, and bill for it. Beyond the limit the failure is still counted and logged — only the handler is skipped.
Example
max_per_minute: 6