Air Pipe Fundamentals
Category: Backend & APIs
This page is generated from the Air Pipe marketplace. Browse it live to install into your organization.
Reference config covering every core Air Pipe concept. Not a production pack — use it as a learning reference and to seed the RAG chatbot with authoritative Air Pipe syntax so it can answer "how do I do X?" questions.
All examples call public APIs (jsonplaceholder.typicode.com) — no database or managed variables required.
What's included
| File | Purpose |
|---|---|
config.yml | Reference config — one endpoint per concept area |
Endpoints
| Method | Path | Concept demonstrated |
|---|---|---|
GET | /fundamentals/input/params | Query param access with a|params |
POST | /fundamentals/input/body | Body access, hide_data_on_success |
GET | /fundamentals/interpolation/demo | All interpolation forms |
POST | /fundamentals/assertions/demo | All assertion types |
POST | /fundamentals/run-conditions/demo | All run condition types |
GET | /fundamentals/transforms/demo | rename, remove, keep, nested_transforms |
GET | /fundamentals/transforms/filter | filter transform with query params |
GET | /fundamentals/metrics/demo | counter and gauge metrics |
Input sources
Air Pipe actions receive input from five sources. Set the source on an action with the input: key.
| Source | When to use |
|---|---|
a|body | Parsed JSON request body (POST/PUT/PATCH) |
a|params | URL query parameters — ?key=value |
a|headers | HTTP request headers (all keys are lowercased) |
a|request | Full envelope: { method, path, headers, params, body } |
a|raw_body | Raw unparsed request body string — required for HMAC verification |
actions:
- name: ReadBody
input: a|body| # capture the parsed JSON body
- name: ReadParams
input: a|params| # capture ?key=value query string
- name: ReadHeaders
input: a|headers| # capture request headers
- name: ReadAll
input: a|request| # full request envelope
Access a specific field from a captured input:
# Simple field
a|ReadBody::title|
# Nested field (dot-path)
a|ReadBody::address.city|
Interpolation
Interpolation resolves values at runtime and injects them into strings, URLs, and SQL params.
# Managed variable (encrypted, never logged — set in the Air Pipe dashboard)
a|ap_var::OPENAI_API_KEY|
# Environment variable (self-hosted deployments only)
a|env::DATABASE_URL|
# Field from a previous action's result
a|ActionName::fieldName|
# Nested field via dot-path
a|ActionName::address.city|
# Wrap in JSON double-quotes (use inside JSON body strings)
a|double_quote(ActionName::title)|
a|double_quote(ap_var::MODEL_NAME)|
# Current Unix timestamp in seconds
a|timestamp|
# Current date as YYYY-MM-DD
a|timestamp:date|
# Raw unparsed request body (for HMAC verification)
a|raw_body|
IMPORTANT — missing fields do not resolve to null. When a field is absent from the source object, the interpolation token passes through as a literal string (e.g. "a|ReadBody::missingField|"). This causes:
- Silent wrong results for text comparisons (the literal string never matches a real value)
- Hard errors for typed casts (
::int,::timestamptz)
For optional SQL parameters, use the JSONB pattern instead of per-field interpolation:
# Pass the entire body as a single JSONB param
params:
- a|ReadFilters|
# In SQL, missing keys return genuine NULL
WHERE ($1::jsonb->>'field' IS NULL OR column = $1::jsonb->>'field')
Assertions
Assertions validate an action's result. Failing an assertion marks the action as failed — downstream actions using run_when_succeeded will not run.
assert:
http_code_on_error: 400 # HTTP status code on failure (default: 500)
error_message: "Bad input" # Response message on failure
tests:
# Presence
- value: email
is_not_null: true
description: "Shown in error context"
# Empty check
- value: name
is_not_empty: true
# Regex
- value: email
regex: "^[^@]+@[^@]+\\.[^@]+$"
# Equality
- value: status
is_equal_to: active
- value: status
is_not_equal_to: banned
# Numeric
- value: age
is_greater_than: 0
- value: score
is_less_than: 100
# Row/item count (database queries and arrays)
- value: count()
is_equal_to: 1
error_message: "Expected exactly one result"
# Null check (field must be absent or null)
- value: deletedAt
is_null: true
# HMAC signature verification
- value: signature_header
is_valid_hmac:
secret: a|ap_var::WEBHOOK_SECRET|
body: "a|ParseSignature::timestamp|.a|raw_body|"
algorithm: sha256
Run conditions
Run conditions control when an action executes relative to prior actions.
run_when_succeeded
Runs only if all listed actions succeeded.
- name: CreateRecord
run_when_succeeded: [ValidateBody]
# With a custom error code if the dependency failed
- name: CreateRecord
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
run_when_failed
Runs as a fallback if the listed actions failed. Useful for alternative data sources or error recovery.
- name: FallbackUser
run_when_failed: [FetchPrimaryUser]
run_when_finished
Runs after the listed actions complete, regardless of success or failure. Use for logging, cleanup, or guaranteed side-effects.
- name: LogAttempt
run_when_finished: [FetchUser]
at_least
Runs if at least N of the listed actions succeeded.
- name: Proceed
run_when_succeeded:
at_least: 2
actions: [StepA, StepB, StepC, StepD]
run_on_assertion
A conditional guard — the action only runs if the named tests pass. Can reference data from any prior action by name. Does not replace the run condition; it layers on top of it.
- name: HandleAdminAction
run_when_succeeded: [FetchUser]
run_on_assertion:
tests:
- action: FetchUser
value: role
is_equal_to: admin
previous keyword
Shorthand for the immediately preceding action in the list.
- name: Step1
...
- name: Step2
run_when_succeeded: [previous] # = run_when_succeeded: [Step1]
Transforms
post_transforms apply sequentially to an action's result after it completes. They are applied in order — each transform receives the output of the previous one.
extract_value
Extract a field or array element from the result.
post_transforms:
- extract_value: body # extract the 'body' field
- extract_value: "[0]" # extract first array element
- extract_value: "choices[0].message.content" # nested path (if supported)
extract_with_regex
Extract named capture groups from a string field.
post_transforms:
- extract_with_regex:
value: stripe-signature # the field to parse
expr: 't=(\d+).*?v1=([a-f0-9]+)'
keys: [timestamp, v1_signature] # names for capture groups
filter
Filter an array by field conditions.
post_transforms:
- filter:
target: $
conditions:
$.title:
contains: "search term" # substring match
$.status: "active" # exact equality match
rename_attributes
Rename fields. Use $[*].old for all elements in an array, $.old for a single object.
post_transforms:
- rename_attributes:
$[*].name: fullName # array
$[*].email: contactEmail
$.username: user # object
remove_attributes
Delete specific fields.
post_transforms:
- remove_attributes:
- $[*].password
- $[*].internalId
- $.secretKey
keep_attributes
Remove everything except the listed fields.
post_transforms:
- keep_attributes:
- $[*].id
- $[*].name
- $[*].email
nested_transforms
Apply transforms inside a nested field.
post_transforms:
- nested_transforms:
$[*].address:
- remove_attributes:
- $.geo
- $.zipcode
bcrypt
Hash a field in-place using bcrypt (for password storage).
post_transforms:
- bcrypt:
value: password
cost: 12 # bcrypt cost factor (default: 10)
Metrics
emit_metric publishes a Prometheus metric, visible at /metrics.
# Counter — increments by 1 on each emit
- name: TrackRequest
hide_data_on_success: true
emit_metric:
name: app_requests_total
type: counter
labels:
endpoint: users
source: api
# Gauge — set to an explicit value on each emit
- name: TrackUserCount
hide_data_on_success: true
emit_metric:
name: app_active_users
type: gauge
value: a|CountUsers::total|
labels:
region: us-east
hide_data_on_success and hide_data_on_error
Use these flags to prevent intermediate action results from appearing in the final API response or in logs.
- name: HashPassword
hide_data_on_success: true # result not returned in response or shown in logs
hide_data_on_error: true # error details (including input) not logged — use for secrets
- name: StoreEvent
hide_data_on_success: true # side-effect only — suppress from response
hide_data_on_error: true is important for actions that handle secrets or PII — it prevents sensitive input from appearing in error logs.
Database patterns
Basic query with params
- name: GetUser
database: main
query: |
SELECT id, email, username FROM users WHERE id = $1;
params:
- a|ValidateBody::id|
post_transforms:
- extract_value: "[0]"
Assert row count
- name: GetUser
database: main
query: |
SELECT id, email FROM users WHERE id = $1;
params:
- a|ValidateBody::id|
assert:
http_code_on_error: 404
error_message: "User not found"
tests:
- value: count()
is_equal_to: 1
post_transforms:
- extract_value: "[0]"
Idempotent insert
- name: StoreEvent
database: main
query: |
INSERT INTO events (external_id, payload)
VALUES ($1, $2::jsonb)
ON CONFLICT (external_id) DO NOTHING
RETURNING id;
params:
- a|ParseBody::id|
- a|ParseBody|
Optional filter params (JSONB pattern)
# Pass the whole body as $1, use ->> to access fields
# Missing JSON keys return genuine SQL NULL — IS NULL checks work correctly
- name: SearchRecords
database: main
query: |
SELECT id, name, status FROM records
WHERE
($1::jsonb->>'status' IS NULL OR status = $1::jsonb->>'status')
AND ($1::jsonb->>'name' IS NULL OR name ILIKE '%' || $1::jsonb->>'name' || '%')
ORDER BY created_at DESC
LIMIT 100;
params:
- a|ReadFilters|
Notes
- The
[previous]keyword in run conditions is shorthand for the immediately preceding action in the action list. run_on_assertioncan reference any prior action by name — it is not limited to the immediately preceding action.at_least: Ninrun_when_succeededandrun_when_failedsets the minimum number of listed actions that must satisfy the condition. Omitting it requires all listed actions to satisfy it.- Transforms are applied in the order they are listed. Each transform receives the output of the previous one.
count()in assertions counts rows from database queries or items in an array response.
Configuration
config.yml
name: AirPipeFundamentals
description: >
Reference config covering every core Air Pipe concept: input sources,
interpolation, assertions, run conditions, transforms, and metrics.
Not a production pack — use it as a learning reference and to seed
the RAG chatbot with authoritative Air Pipe syntax.
docs: true
# No managed variables required — all examples call public APIs or are self-contained.
interfaces:
# ── Input sources ─────────────────────────────────────────────────────────
#
# Air Pipe actions receive input from five sources:
#
# a|body| — parsed JSON request body (POST/PUT/PATCH)
# a|params| — URL query parameters (?key=value)
# a|headers| — HTTP request headers (keys are lowercased)
# a|request| — full request envelope: { method, path, headers, params, body }
# a|raw_body| — raw request body as a string (needed for HMAC verification)
#
# Set the input on an action with: input: a|body|
# Access a specific field with: a|ActionName::fieldName|
# Access a nested field with: a|ActionName::outer.inner|
# GET /fundamentals/input/params?userId=1&format=json
# Demonstrates a|params| for query string access.
fundamentals/input/params:
output: http
summary: Query param access
description: Access URL query parameters with a|params|. GET /fundamentals/input/params?userId=1
tags: [fundamentals, input]
response_example:
userId: "1"
format: "json"
actions:
- name: ReadParams
input: a|params|
assert:
http_code_on_error: 400
tests:
- value: userId
is_not_null: true
description: "userId query param is required"
- name: FetchUser
run_when_succeeded: [ReadParams]
http:
url: https://jsonplaceholder.typicode.com/users/a|ReadParams::userId|
headers:
content-type: application/json
post_transforms:
- extract_value: body
# POST /fundamentals/input/body
# Demonstrates a|body|, field access, and hide_data_on_success.
fundamentals/input/body:
output: http
method: POST
summary: Body + header access
description: Access parsed JSON body and request headers from a single action.
tags: [fundamentals, input]
request_example:
title: Hello
userId: 1
actions:
# input: a|body| captures the parsed JSON body.
# input: a|headers| would capture request headers.
# input: a|request| captures the full envelope (method, path, headers, params, body).
- name: ReadBody
input: a|body|
hide_data_on_success: true
assert:
http_code_on_error: 400
tests:
- value: title
is_not_null: true
- value: userId
is_not_null: true
- name: CreatePost
run_when_succeeded:
actions: [ReadBody]
http_code_on_error: 400
http:
url: https://jsonplaceholder.typicode.com/posts
method: POST
headers:
content-type: application/json
body: |
{
"title": a|double_quote(ReadBody::title)|,
"body": "Created via Air Pipe fundamentals",
"userId": a|ReadBody::userId|
}
post_transforms:
- extract_value: body
# ── Interpolation ──────────────────────────────────────────────────────────
#
# Interpolation syntax: a|<source>| or a|<source>::<field>|
#
# a|ap_var::NAME| — managed variable (encrypted, never logged)
# a|env::NAME| — environment variable (self-hosted only)
# a|ActionName::field| — field from a previous action's result
# a|ActionName::outer.inner| — nested field via dot-path
# a|double_quote(X)| — interpolate X and wrap in JSON double-quotes
# Use inside JSON body strings to avoid injection.
# a|timestamp:s| — current Unix timestamp (seconds)
# a|timestamp:date| — current date as YYYY-MM-DD
# a|raw_body| — raw unparsed request body string
#
# IMPORTANT: When a field is absent from the source, the interpolation token
# is passed as a literal string — it does NOT resolve to null.
# For optional parameters in SQL, use the JSONB pattern:
# $1::jsonb->>'field' IS NULL (missing JSON keys return genuine SQL NULL)
# GET /fundamentals/interpolation/demo
# Shows all common interpolation forms in one place.
fundamentals/interpolation/demo:
output: http
summary: Interpolation reference
description: Demonstrates every interpolation form available in Air Pipe configs.
tags: [fundamentals, interpolation]
actions:
- name: FetchPost
http:
url: https://jsonplaceholder.typicode.com/posts/1
post_transforms:
- extract_value: body
- name: FetchUser
run_when_succeeded: [FetchPost]
http:
# a|ActionName::field| — use a field from FetchPost's result
url: https://jsonplaceholder.typicode.com/users/a|FetchPost::userId|
post_transforms:
- extract_value: body
- name: BuildSummary
run_when_succeeded: [FetchUser]
http:
url: https://jsonplaceholder.typicode.com/posts
method: POST
headers:
content-type: application/json
body: |
{
"postTitle": a|double_quote(FetchPost::title)|,
"authorName": a|double_quote(FetchUser::name)|,
"authorEmail": a|double_quote(FetchUser::email)|,
"generatedAt": a|timestamp:s|
}
post_transforms:
- extract_value: body
# ── Assertions ────────────────────────────────────────────────────────────
#
# Assertions validate action results. If any test fails the action fails,
# and downstream actions using run_when_succeeded will not run.
#
# Available tests:
# is_not_null: true — field must be present and non-null
# is_null: true — field must be null or absent
# is_not_empty: true — field must not be empty string/array
# is_equal_to: <value> — strict equality
# is_not_equal_to: <value>
# is_greater_than: <number> — numeric comparison
# is_less_than: <number>
# regex: "^pattern$" — value must match the regex
# count() — number of rows/items in the result
# is_valid_hmac: — HMAC signature verification
# secret: <value>
# body: <signed-string>
# algorithm: sha256
#
# Error control:
# http_code_on_error: 400 — HTTP status code returned on assertion failure
# error_message: "..." — message returned on failure
# description: "..." — field-level description shown in error context
# POST /fundamentals/assertions/demo
# Shows every assertion type with realistic examples.
fundamentals/assertions/demo:
output: http
method: POST
summary: Assertions reference
description: Demonstrates every assertion type — field validation, regex, count, and HMAC.
tags: [fundamentals, assertions]
request_example:
email: [email protected]
age: 25
role: admin
actions:
- name: ValidateFields
input: a|body|
hide_data_on_success: true
assert:
http_code_on_error: 400
tests:
# Null / presence checks
- value: email
is_not_null: true
is_not_empty: true
description: "Email address is required"
# Regex — enforce email format
- value: email
regex: "^[^@]+@[^@]+\\.[^@]+$"
description: "Must be a valid email address"
# Numeric comparison
- value: age
is_not_null: true
is_greater_than: 0
description: "Age must be a positive number"
# Equality
- value: role
is_equal_to: admin
description: "Only admin role is accepted by this endpoint"
- name: FetchUsers
run_when_succeeded: [ValidateFields]
http:
url: https://jsonplaceholder.typicode.com/users
post_transforms:
- extract_value: body
- name: AssertCount
run_when_succeeded: [FetchUsers]
input: a|FetchUsers|
assert:
http_code_on_error: 502
error_message: "Expected exactly 10 users from upstream"
tests:
# count() checks the number of rows/items in the current result
- value: count()
is_equal_to: 10
# ── Run conditions ────────────────────────────────────────────────────────
#
# Run conditions control when an action executes relative to prior actions.
#
# run_when_succeeded: [ActionA, ActionB]
# — runs only if ALL listed actions succeeded.
# Use http_code_on_error to set the response code if this chain fails.
#
# run_when_failed: [ActionA]
# — runs only if ALL listed actions failed. Useful for fallbacks.
#
# run_when_finished: [ActionA]
# — runs after ActionA completes, regardless of success or failure.
#
# run_when_succeeded:
# at_least: 2
# actions: [A, B, C, D]
# — runs if AT LEAST N of the listed actions succeeded.
#
# run_on_assertion:
# — conditional guard: action only runs if the listed tests pass.
# Can reference data from any prior action by name.
#
# Special keyword: [previous]
# Shorthand for the immediately preceding action in the list.
# POST /fundamentals/run-conditions/demo
fundamentals/run-conditions/demo:
output: http
method: POST
summary: Run conditions reference
description: >
Demonstrates all run condition types: run_when_succeeded, run_when_failed,
run_when_finished, at_least, and run_on_assertion.
tags: [fundamentals, workflow]
request_example:
userId: 1
actions:
- name: ValidateBody
input: a|body|
hide_data_on_success: true
assert:
tests:
- value: userId
is_not_null: true
# run_when_succeeded — only runs if ValidateBody passed.
- name: FetchUser
run_when_succeeded:
actions: [ValidateBody]
http_code_on_error: 400
http:
url: https://jsonplaceholder.typicode.com/users/a|ValidateBody::userId|
post_transforms:
- extract_value: body
# run_when_failed — runs as a fallback if FetchUser failed.
- name: FallbackUser
run_when_failed: [FetchUser]
http:
url: https://jsonplaceholder.typicode.com/users/1
post_transforms:
- extract_value: body
# run_when_finished — runs after FetchUser regardless of success or failure.
# Good for cleanup, logging, or guaranteed side-effects.
- name: FetchUserPosts
run_when_finished: [FetchUser]
http:
url: https://jsonplaceholder.typicode.com/posts?userId=a|ValidateBody::userId|
post_transforms:
- extract_value: body
# run_on_assertion — conditional guard. Only runs if the named action's
# field passes the test. Does not affect the parent run condition.
- name: FetchAdminData
run_when_succeeded: [FetchUser]
run_on_assertion:
tests:
- action: FetchUser
value: id
is_equal_to: 1
http:
url: https://jsonplaceholder.typicode.com/users
post_transforms:
- extract_value: body
# ── Transforms ────────────────────────────────────────────────────────────
#
# post_transforms apply sequentially to the action's result after it completes.
#
# extract_value: "[0]" — extract first array element
# extract_value: fieldName — extract a field by name
# extract_value: "field.nested" — extract a nested field
#
# extract_with_regex: — extract capture groups into named keys
# value: fieldName
# expr: 't=(\d+).*?v1=([a-f0-9]+)'
# keys: [timestamp, signature]
#
# filter: — filter array by field conditions
# target: $
# conditions:
# $.fieldName:
# contains: "search term" — substring match
# $.otherField: "exact match" — equality match
#
# rename_attributes: — rename keys (array or object)
# $[*].oldName: newName — across all array elements
# $.oldName: newName — on a single object
#
# remove_attributes: — delete keys from the result
# - $[*].sensitiveField
# - $.internalId
#
# keep_attributes: — remove everything EXCEPT listed keys
# - $[*].id
# - $[*].name
#
# nested_transforms: — apply transforms inside a nested field
# $[*].address:
# - remove_attributes:
# - $.geo
#
# bcrypt: — hash a field in-place
# value: password
# cost: 12
# GET /fundamentals/transforms/demo
fundamentals/transforms/demo:
output: http
summary: Transforms reference
description: >
Demonstrates extract_value, filter, rename_attributes, remove_attributes,
keep_attributes, and nested_transforms — applied to a real API response.
tags: [fundamentals, transforms]
response_example:
- fullName: Leanne Graham
contactEmail: [email protected]
city: Gwenborough
actions:
- name: FetchUsers
http:
url: https://jsonplaceholder.typicode.com/users
post_transforms:
# 1. Extract the response body from the HTTP envelope
- extract_value: body
# 2. Rename: name → fullName, email → contactEmail across all elements
- rename_attributes:
$[*].name: fullName
$[*].email: contactEmail
# 3. Remove fields we don't want in the response
- remove_attributes:
- $[*].phone
- $[*].website
- $[*].company
# 4. Clean up the nested address object — remove geo + suite
- nested_transforms:
$[*].address:
- remove_attributes:
- $.geo
- $.suite
- $.zipcode
# 5. Keep only the fields we want in the final output
- keep_attributes:
- $[*].fullName
- $[*].contactEmail
- $[*].address.city
# GET /fundamentals/transforms/filter
# Demonstrates the filter transform with query params as the search input.
fundamentals/transforms/filter:
output: http
summary: Filter transform
description: Filter an array by field value. GET /fundamentals/transforms/filter?title=sunt
tags: [fundamentals, transforms]
actions:
- name: ReadParams
input: a|params|
assert:
http_code_on_error: 400
tests:
- value: title
is_not_null: true
- name: FetchAndFilter
run_when_succeeded: [ReadParams]
http:
url: https://jsonplaceholder.typicode.com/posts
post_transforms:
- extract_value: body
- filter:
target: $
conditions:
$.title:
contains: a|ReadParams::title|
# ── Metrics ───────────────────────────────────────────────────────────────
#
# emit_metric publishes a Prometheus metric. Metrics are available at /metrics.
#
# name: metric_name — metric name (snake_case)
# type: counter — counter (monotonically increasing)
# type: gauge — gauge (can go up or down)
# value: a|Action::field| — for gauges, the current value
# labels: — optional key/value dimensions
# key: value
#
# Counters are auto-incremented on each emit.
# Gauges require an explicit value.
# GET /fundamentals/metrics/demo
fundamentals/metrics/demo:
output: http
summary: Metrics reference
description: Demonstrates emit_metric for both counter and gauge types.
tags: [fundamentals, metrics]
actions:
- name: FetchUsers
http:
url: https://jsonplaceholder.typicode.com/users
post_transforms:
- extract_value: body
# Counter — increments by 1 on each request.
# Visible at /metrics as: app_users_listed_total{interface="fundamentals/metrics/demo"}
- name: TrackListRequest
run_when_succeeded: [FetchUsers]
hide_data_on_success: true
emit_metric:
name: app_users_listed_total
type: counter
labels:
source: api
# Gauge — set to the current value on each request.
# Useful for tracking sizes, counts, or any value that can decrease.
- name: TrackUserCount
run_when_succeeded: [FetchUsers]
hide_data_on_success: true
emit_metric:
name: app_users_gauge
type: gauge
value: a|FetchUsers::count()|