Concepts
Pipe types
Single-provider pipes
A single-provider pipe runs on one third-party service (e.g. LeadMagic, ZeroBounce) or on Pipe0 itself (native pipes).
Waterfall pipes
A waterfall pipe tries providers in order. The first one that returns data wins; the rest are skipped.
Only the provider that returns a result is billed — no_result is free.
Waterfalls boost coverage at the cost of latency: each provider attempted adds a round-trip.
Choosing waterfall providers
Set config.providers to an array of { provider: <PROVIDER_NAME> }. Providers run in array order — index 0 first. Remove an entry to skip it, reorder to change priority. Omit config.providers to use the default order.
Field mode
field_mode determines the shape of a pipe's config object.
Field mode: static
The pipe's inputs and outputs are fixed. You can remap inputs and rename outputs, but the schema is known up front.
Example: person:workemail:waterfall@1 reads name + company_domain and writes work_email.
Rename outputs
Set config.output_fields.<FIELD>.alias:
fetch('https://api.pipe0.com/v1/pipes/run', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
pipes: [
{
pipe_id: 'person:name:split@1',
config: {
output_fields: {
first_name: { alias: 'special_first_name' }
}
}
}
],
input: [{ id: '1', name: 'John Doe' }]
})
})Disable outputs
Set config.output_fields.<FIELD>.enabled: false:
fetch('https://api.pipe0.com/v1/pipes/run', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
pipes: [
{
pipe_id: 'person:name:split@1',
config: {
output_fields: {
first_name: { enabled: false }
}
}
}
],
input: [{ id: '1', name: 'John Doe' }]
})
})Field mode: config
You declare inputs and outputs as part of the pipe's config — the pipe doesn't know them ahead of time.
Templates
Templates are the most common config-mode pattern: you write inputs and outputs as text. Useful for LLM prompts.
Not every template supports outputs.
Example: prompt:run@1 requires both input and output tags. email:write@1 supports only input tags.
Tag syntax
Templates use a whitelisted subset of Liquid for input refs, secrets, constants, control flow, and output declarations.
Input refs
| Form | Meaning |
|---|---|
{{ field }} | Required input. Pipe fails if missing. |
{{ field | default: "" }} | Optional input. The editor inserts new tags this way. |
{{ user.email }} | Dotted access. Only the head (user) is tracked as a dependency. |
{% if email %}{{ email }}{% endif %} | Presence guard — also marks email as optional. |
A field referenced anywhere without a guard becomes required. Only bare {% if x %} and x != nil / x == nil count as guards — compound conditions like {% if a or b %} do not.
Secrets and constants
Authorization: Bearer {{ SECRETS.API_KEY }}
Promo code: {{ CONSTANTS.PROMO }}- Keys must match
^[A-Z][A-Z0-9_]*$. - Lowercase (
{{ secrets.x }}) is treated as a regular input ref. - Missing secrets/constants throw at render time.
- Secret values are not re-parsed as Liquid.
Control flow
Allowed: if, elsif, else, unless, for, break, continue. Max for nesting is 3. forloop.index, forloop.first, etc. are available.
Filters (whitelist)
json, escape, escape_once, default, upcase, downcase, size, join, first, last, replace, truncate, strip, newline_to_br, url_encode. Anything else throws at render time.
Not allowed
{% assign %}, {% capture %}, {% include %}, {% case %}, {% cycle %}, arithmetic and date filters, and other standard Liquid features. Disallowed tags fail at parse time.
Output declarations
In prompt-style fields, declare model outputs with {% output %}:
{% output summary, type: "string", description: "One-line summary." %}
{% output score, type: "number", description: "Confidence from 0 to 100.", required: false %}
{% output details, type: "json", schema: "ProductDetails", description: "Full product spec." %}| Arg | Required | Notes |
|---|---|---|
name | yes | First positional. ^[a-zA-Z_][a-zA-Z0-9_]*$. |
type | yes | "string", "number", "boolean", "json", etc. |
description | yes | Shown to the model. |
required | no | true / false. Defaults to true. |
format | no | E.g. "email", "url". |
schema | no | Key into json_schemas; used with type: "json". |
{% output %} is only valid in fields that support it (e.g. prompt inputs) — using it elsewhere fails at save time.
Template example
{
"prompt": {
"template": "Determine if {{ company_name }} is a good customer for my magic wand 🪄 business. {% output reason, type: \"json\", schema: \"reason\", description: \"Explain why the customer is a good prospect\" %} {% output is_good_customer, type: \"boolean\", description: \"Overall fit assessment\" %}",
"json_schemas": {
"reason": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "A summary of why the customer is or isn't a good prospect."
}
}
}
}
}
}Migration from the old syntax
The previous {{ input ... }} / {{ secret ... }} / {{ output ... }} / <tag>...</tag> syntax does not auto-migrate.
| Old | New |
|---|---|
{{ input field }} | {{ field }} (required) or {{ field | default: "" }} (optional) |
{{ secret api_key }} | {{ SECRETS.API_KEY }} |
{{ output X type="string" description="…" }} | {% output X, type: "string", description: "…" %} |
<tag>…</tag> blocks | rewrite as {{ … }} refs and Liquid control flow |
Run-if
run_if runs a pipe only when its conditions match. For example:
Run
person:name:split@1if the value ofnamecontainsTom.
Request:
{
"pipes": [
{
"pipe_id": "person:name:split@1",
"run_if": {
"action": "run",
"when": {
"logic": "and",
"conditions": [
{
"field_name": "name",
"property": "value",
"operator": "contains",
"value": "Tom"
}
]
}
}
}
],
"input": [...]
}Schema
action
What to do when conditions match. Defaults to "run". Reserved for future expansion.
Supported: run.
run_if doesn't override other gating. A pipe with missing required inputs is still skipped even if its run_if matches.
when.logic
How to combine conditions.
Supported: and (all must match), or (any matches).
when.conditions[]
The conditions to evaluate. Each checks a field's value or status; combined with when.logic.
condition.field_name
The field to evaluate. Either an input field or another pipe's output_field.
JSON fields can't be targeted directly. Expand JSON properties into their own fields first.
condition.property
Which aspect of the field to compare.
Supported: value (the field's value), status (its resolution status).
condition.operator
How to compare. Allowed operators depend on property and field.type:
| Property | Type | Operators |
|---|---|---|
| value | string | eq, neq, contains |
| value | date | eq, neq, lt, lte, gt, gte, contains |
| value | number | eq, neq, lt, lte, gt, gte, contains |
| value | boolean | eq, neq |
| status | any | eq, neq |
condition.value
What to compare against.
- For
property: "value": a primitive (string, number, boolean, or null). - For
property: "status": one ofcompleted,no_result,failed,skipped.