logo-darkPipe0
Advanced

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:

Rename an output field
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:

Disable an output field
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

FormMeaning
{{ 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." %}
ArgRequiredNotes
nameyesFirst positional. ^[a-zA-Z_][a-zA-Z0-9_]*$.
typeyes"string", "number", "boolean", "json", etc.
descriptionyesShown to the model.
requirednotrue / false. Defaults to true.
formatnoE.g. "email", "url".
schemanoKey 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 config with output declarations
{
  "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.

OldNew
{{ 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> blocksrewrite as {{ … }} refs and Liquid control flow

Run-if

run_if runs a pipe only when its conditions match. For example:

Run person:name:split@1 if the value of name contains Tom.

Request:

Request with run_if
{
  "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:

PropertyTypeOperators
valuestringeq, neq, contains
valuedateeq, neq, lt, lte, gt, gte, contains
valuenumbereq, neq, lt, lte, gt, gte, contains
valuebooleaneq, neq
statusanyeq, neq

condition.value

What to compare against.

  • For property: "value": a primitive (string, number, boolean, or null).
  • For property: "status": one of completed, no_result, failed, skipped.

On this page